Sometimes in a WPF or Windows Store or Windows Phone application we need to draw some things based on some collection of data items. Suppose we have the following simple data item:
Suppose we have a collection of CarData objects, and the requirement was to show a set of images along a line with a particular distance, like in the following screenshot:
The distance from the left is determined by the Distance property, and the image is determined by the Image property. How would we achieve that?
One obvious option is to use a loop that iterates through the CarData collection, and adding (e.g.) Image elements to a Canvas (by calling the Children.Add method), while setting the Canvas.Left attached property to a value based on the Distance property.
Although this technically works, it’s inelegant to say the least. If a new CarData object is added or removed, we’d have to rerun the loop to regenerate the images or manually add/remove the appropriate image. Dealing directly with the UI instead of just with the data object is just looking for trouble.
The obvious solution to the problem is data binding; it’s classic – bind the data objects to some UI using appropriate binding expressions, and just deal with the data objects; forget about the UI; let the data bindings managed everything automatically.
But how can we bind the collection of CarData objects to a Canvas’ Children? We can’t – the Children property is not bindable.
The solution is to use an ItemsControl control (or one of its derivatives) and bind its ItemsSource property to the collection. The problem with ItemsControl (and its derivatives) is that they display items in a vertical StackPanel (or VirtualizingStackPanel, but that’s unimportant for this discussion). How can we change that? Easy – use the ItemsPanel property:
We change the ItemsPanel to a Canvas and provide a DataTemplate (in the ItemTemplate property) that consists of an Image moved to the correct distance with the Canvas.Left attached property bound to the CarData.Distance property. Running this produces the following:
It’s certainly disappointing; it seems the Canvas.Left property had no effect. Can you spot the problem?
The problem is a subtle one: Every item in a ListBox is hosted in a ListBoxItem control. This means changing the Image’s Canvas.Left property has no effect, because the Image is not in any Canvas; the ListBoxItem is.
Fixing this is just a matter of applying an ItemContainerStyle that has the required properties; the Image element has no use for them:
The Canvas.Left property is applied on the container item – a ListBoxItem in this case.
This little trick can help to produce interesting results in an ItemsControl-derived control without the expected “list” look.