Oh look… I did it again! I promised to write ‘G’ is for Generator and then I come out with ‘I’ is for Item Container. I’m like a bad TV series that just keeps leading you on… Then just when you think you’re about to discover the true identity of the evil mastermind, the plot takes an unexpected turn. Please tune in next time when we’ll unveil… The Generator!
Okay, this episode isn’t really a departure from the plan. I simply realized that I had too much content for a single post, so I broke our look at item containers and item container generation into two separate issues. If it makes you feel better, you can think of this as ‘G’ is for Generator, Part I. Although we won’t actually talk about container “generation” in this episode, we will lay the groundwork by talking about the containers that get “generated”.
In our last episode, ‘P’ is for Panel, we discovered that an ItemsControl leverages a panel to layout its children. We call this panel the items host (or the items panel). It seems quite appropriate to use a panel to layout the items, since that is exactly the purpose for which a panel is designed… namely, to size and position a collection of visual children.
In an earlier article, ‘D’ is for DataTemplate, we saw that a data template can be used to specify the visuals that represent an item within the Items collection of an ItemsControl. And since any object can belong to the Items collection, this architecture allows for a diverse and disparate collection of visuals within an ItemsControl.
A Motley Crew of Items
Consider the following example:
<ItemsControl HorizontalAlignment="Left">
<TextBox Name="tb" Margin="2" Text="Test" />
<sys:String>http://drwpf.com/blog/</sys:String>
<sys:String>http://forums.microsoft.com/MSDN/</sys:String>
<x:Static Member="ApplicationCommands.Copy" />
<x:Static Member="ApplicationCommands.Cut" />
<x:Static Member="ApplicationCommands.Paste" />
<x:Static Member="ApplicationCommands.SelectAll" />
</ItemsControl>
This ItemsControl has 7 items explicitly added to its Items collection: one TextBox, two strings, and four routed commands. You could easily define a data template for the String type to display the strings as hyperlinks and another data template for the RoutedUICommand type to display the commands as buttons. Then the ItemsControl might have the visual representation shown here.
Since a StackPanel is the default items host for an ItemsControl, the children are nicely stacked. If you’d like to observe this example in Kaxaml (or XamlPad if you’re old school), the very simple markup is available here.
Some Common Problems to Consider
Below are several common problems that need to be considered when working with an ItemsControl in WPF. We should keep these in mind as we look at item containers in this post and item container generators in the next episode.
Problem 1: Custom Child Placement
A panel is capable of arranging all types of UI elements, so it can certainly handle such a motley crew of children, but imagine that the panel is a Canvas and you want to provide custom placement of the items within your collection. In this case, you would need to set the attached Canvas properties (Canvas.Left, Canvas.Top, etc) on all of the differing elements in your collection of children. This could be a real hassle to maintain with so many different types of visuals.
Problem 2: Mappings between Items and Visuals
And remember that the actual items may simply be string or command objects. These objects have no inherent visual representation without their data templates. Once a data template has been inflated for an item and the visuals have been added to your ItemsControl, how do you map the visuals back to the items and vice versa?
Problem 3: UI Virtualization
What if there are thousands of items in your ItemsControl? Unless the items are very small, they will not all appear within the viewport of the control at the same time. We definitely do not want to pay a high performance penalty for instantiating visuals for items that are not visible. How can we make sure that only visuals for the visible items (give or take a few) are in memory at any given moment?
Problem 4: Consistent Item Chrome
Another thing that you might want to do in an ItemsControl is provide a common “chrome” for each item. Since the items themselves can be quite diverse and the items panel might not be something as predictable as a StackPanel, an ItemsControl might sometimes appear haphazard. One way to bring a sense of uniformity to such a collection is to provide a consistent background or chrome for each item. Is it possible to do this without directly adding the chrome to the item’s data template?
Problem 5: Visible Selection State
Finally, if the ItemsControl is a Selector (e.g., ListBox, ListView, TreeView, ComboBox, etc), how would you go about showing a uniform selection state for all of the differing children?
It would certainly be a lot easier to deal with all of the above issues if the children of the items panel were all the same type of element. Enter the item container…
What is an item container?
An item container is an automatically generated “wrapper” element for items within an ItemsControl. It is called an item container because it actually “contains” an item from the Items collection. More specifically, the container is the control which contains the visual representation for an item. If the item has a data template, the container is the control into which that data template is inflated.
Let’s revisit a simple ListBox example that we saw earlier in ‘D’ is for DataTemplate. Here is a ListBox that displays a collection of Characters:
<ListBox ItemsSource="{Binding Source={StaticResource Characters}}" />
Note that we’re using a ListBox in ItemsSource Mode (see ‘C’ is for Collection). The collection of characters is the same as before:
<src:CharacterCollection x:Key="Characters">
<src:Character First="Bart" Last="Simpson" Age="10"
Gender="Male" Image="images/bart.png" />
<src:Character First="Homer" Last="Simpson" Age="38"
Gender="Male" Image="images/homer.png" />
<src:Character First="Lisa" Last="Bouvier" Age="8"
Gender="Female" Image="images/lisa.png" />
<src:Character First="Maggie" Last="Simpson" Age="0"
Gender="Female" Image="images/maggie.png" />
<src:Character First="Marge" Last="Bouvier" Age="38"
Gender="Female" Image="images/marge.png" />
</src:CharacterCollection>
We can define a very simple data template to display the characters:
<DataTemplate DataType=" {x:Type src:Character} ">
<StackPanel Orientation="Vertical" Margin="5">
<TextBlock FontWeight="Bold" Text="{Binding First}"
TextAlignment="Center" />
<Image Margin="0,5,0,0" Source="{Binding Image}" />
</StackPanel>
</DataTemplate>
This gives us the ListBox at the right.
Where’s the container?
Supposedly, the visuals for each of the characters in this example are wrapped within an item container. But I don’t see a container! Where is the container? More importantly, what is the container? The answer to that question actually depends on the ItemsControl. In this case, the ItemsControl is a ListBox. The item container for a ListBox happens to be a control called ListBoxItem.
You may not think you see a ListBoxItem in the control, but if you select an item, you will notice that the background of the entire selected item becomes blue and the TextBlock within the selected item shows up with a white Foreground (see the image below). The blue that you are seeing here is the background of the item container.
These visual changes happen automatically without any changes to our Character data template. They are the result of the template within the default style for ListBoxItem, (along with some triggers in that template).
Wow! The container has a pretty important role in this scenario, especially if you think you might like to alter the visuals used to depict item selection. Clearly, this merits further investigation…
Understanding the Item Container and its Style
As just mentioned, the selection state for a ListBoxItem is defined within the control’s style and template. Anytime you are working with an ItemsControl, I strongly recommend that you take time to understand the control’s item container as well as the default style for that container. So let’s just take a moment to look at some aspects of ListBoxItem and the default ListBoxItem style, as defined for the Vista Aero theme (from Aero.NormalColor.xaml).
- The Background of the ListBoxItem is set to Transparent. This is important. By using a Transparent brush rather than the default null brush, the ListBoxItem becomes hittable (or visible to hittesting by input devices). In other words, a mouse hittest will find the item, thereby allowing it to be selected when the transparent portion is clicked.
- HorizontalContentAlignment and VerticalContentAlignment on the ListBoxItem are data bound to the properties of the same names on ListBox. As such, if you’d like all ListBoxItems to left-align their content, you can simply set HorizontalContentAlignment to Left on the ListBox itself. This is very handy to know and you probably wouldn’t know it without looking at the style.
- The default template for ListBoxItem consists of nothing more than a ContentPresenter within a Border.
- ListBoxItem exposes a dependency property called IsSelected. This is pretty common for the item container of a Selector control. In fact, the Selector class is where the IsSelected property is originally registered with the property engine. ListBoxItem and other containers simply add themselves as owners for the property. As such, Selector.IsSelected provides a useful trigger property for showing that a container is selected.
- There are indeed several triggers within the control template that alter the container’s appearance based on whether it is selected, active, and/or enabled.
Sidenote: If you are new to styling and templating in WPF, recognize that all of the native control styles and templates are available in theme files that ship as part of the framework SDK or with Blend. There are actually many different ways you can view these styles, as I describe in this forum post. Designers often go straight to a tool like Blend, when they want to explore/modify a control template. This is certainly fine too, but I prefer going to the theme file so I can see both the style and template declarations together.
The ItemContainerStyle Property
That’s great! Now we understand the default style and template. What can we do with this knowledge? Well, quite a bit, actually. It turns out that it’s very easy to define our own item container style. We simply need to set the ItemContainerStyle property of the ItemsControl, as shown here:
<ListBox ItemsSource="{Binding Source={StaticResource Characters}}"
ItemContainerStyle="{StaticResource CharacterContainerStyle}" />
Next, we need to define the style. We will use the container style to add some standard chrome to the items in our ListBox by redefining the ListBoxItem’s template, as shown below. You don’t need to get too wrapped up in the nitty gritty of this style (unless that’s your thing). Just note that there are a handful of properties being set, and one of them happens to be the Template property.
<Style x:Key="CharacterContainerStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="Background" Value="#FF3B0031" /> <Setter Property="FocusVisualStyle" Value="{x:Null}" /> <Setter Property="Width" Value="75" /> <Setter Property="Margin" Value="5,2" /> <Setter Property="Padding" Value="3" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Grid> <Rectangle StrokeThickness="1" Stroke="Transparent" RadiusX="5" RadiusY="5" Fill="White" /> <Grid> <Rectangle x:Name="BackgroundRect" Opacity="0.5" StrokeThickness="1" Stroke="Transparent" RadiusX="5" RadiusY="5" Fill=" {TemplateBinding Background} " /> <Rectangle StrokeThickness="1" Stroke="Black" RadiusX="3" RadiusY="3" > <Rectangle.Fill> <LinearGradientBrush StartPoint="-0.51,0.41" EndPoint="1.43,0.41"> <LinearGradientBrush.GradientStops> <GradientStop Color="Transparent" Offset="0"/> <GradientStop Color="#60FFFFFF" Offset="1"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Grid> <Grid.RowDefinitions> <RowDefinition Height="0.6*"/> <RowDefinition Height="0.4*"/> </Grid.RowDefinitions> <Rectangle RadiusX="3" RadiusY="3" Margin="3" Grid.RowSpan="1" Grid.Row="0" > <Rectangle.Fill> <LinearGradientBrush EndPoint="0,0" StartPoint="0,1"> <GradientStop Color="#44FFFFFF" Offset="0"/> <GradientStop Color="#66FFFFFF" Offset="1"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> </Grid> <ContentPresenter x:Name="ContentHost" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> <Rectangle Fill="{x:Null}" Stroke="#FFFFFFFF" RadiusX="3" RadiusY="3" Margin="1" /> </Grid> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
With the above item container style, our ListBox now renders as shown here.
Notice that in this style we have added a setter to explicitly set the Width of the ListBoxItem to 75 device independent pixels. Previously, the item containers were sizing to their content, which meant that each item would render as big as necessary to display the characters name and the image of that character at its natural size (the size stored in the image file).
The container style is a great place to apply sizing because it allows us to provide a consistent size for all items in the ListBox. We could certainly hard code this size into the Character data template, but keep in mind that we may be using the same data template in other places within the application. By putting a Width setter in the container style, rather than explicitly setting the width in the data template, we keep the data template dynamic.
So now we have some consistent chrome and it is nicely defined in the container’s template rather than in the item’s data template. Unfortunately, there is a big problem with this template. When I snapped this image of the ListBox, the selected item was Homer. Of course, you will have to take my word for it, since there is clearly nothing in the visual appearance that can be used to verify I’m telling the truth.
Recall that the default ListBoxItem template is what gave us visual cues for things like selection state. Since we have defined our own ListBoxItem template, we need to do likewise in our template. So let’s just add the following Triggers to our control template:
<ControlTemplate.Triggers>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter TargetName="BackgroundRect" Property="Opacity" Value="1" />
<Setter TargetName="ContentHost" Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="White" GlowSize="9" />
</Setter.Value>
</Setter>
<Setter TargetName="BackgroundRect" Property="Opacity" Value="1" />
</Trigger>
</ControlTemplate.Triggers>
Now when we select Homer, the chrome around him darkens and he glows like an angel (or maybe like he’s radioactive, which is actually more appropriate given his line of work).
The Container’s Data Context is the Item
In ‘D’ is for DataTemplate, we learned that the data context for the root element of the data template is actually the data item that the template represents. And since the DataContext is inherited through the element tree, each child element in the template also has this same data context. This makes establishing bindings on elements in the template very easy. For example, in our Character template, the Text property of the TextBlock is bound to the character’s name by simply doing this:
<TextBlock Text="{Binding First}" />
Well, now we can explain how this actually works. When the item container is generated, the framework sets its data context to the item that the container contains. It then inflates the data template as the content of the container. The elements in the container then naturally inherit their data contexts from the container.
Armed with this knowledge that the DataContext of the item container is the item it contains, we might want to add a data trigger to our style to show the female characters with a pink background color. We do live in a stereotyped world, after all! The following trigger should work nicely:
<Style.Triggers>
<DataTrigger Binding="{Binding Gender} " Value="Female">
<Setter Property="Background" Value="#FFF339CB" />
</DataTrigger>
</Style.Triggers>
Custom Placement of Items within an ItemsControl
Now let’s make just one more change to this sample. It is actually pretty common to add extra metadata to the view model of a WPF application to help position and visualize data. Suppose we modify the Character item in our view model to allow each character to expose its own notion of where it should be positioned in x-y space. To do this, we will add the following Location property to the Character class:
private Point _location = new Point();
public Point Location
{
get { return _location; }
set
{
_location = value;
RaisePropertyChanged ("Location");
}
}
Similarly, we’ll modify our data collection to set the position of the characters:
<src:CharacterCollection x:Key="Characters">
<src:Character First="Bart" Last="Simpson" Age="10"
Gender="Male" Image="images/bart.png" Location="25,150" />
<src:Character First="Homer" Last="Simpson" Age="38"
Gender="Male" Image="images/homer.png" Location="75,0" />
<src:Character First="Lisa" Last="Bouvier" Age="8"
Gender="Female" Image="images/lisa.png" Location="125,150" />
<src:Character First="Maggie" Last="Simpson" Age="0"
Gender="Female" Image="images/maggie.png" Location="225,150" />
<src:Character First="Marge" Last="Bouvier" Age="38"
Gender="Female" Image="images/marge.png" Location="175,0" />
</src:CharacterCollection>
The default items host for a ListBox is a panel called VirtualizingStackPanel. This works great when you want a traditionally stacked layout with the added benefits of UI virtualization, but what if you want a custom layout? In ‘P’ is for Panel, we learned that we can actually choose any panel to serve as the items host for our data items.
Since our Character item now provides its own (x, y) location, a Canvas is the logical choice for an items panel:
<ListBox ItemsSource="{Binding Source={StaticResource Characters}}"
ItemContainerStyle="{StaticResource CharacterContainerStyle}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Hmmm… Now when we run this sample, we see just Marge. Oops! There are still 5 items, but they are all positioned in the same (0, 0) location, so we only see the topmost item. We really want each item to be positioned according to its Location property. That is, we want the Canvas.Left and Canvas.Top properties of each item to be bound to Location.X and Location.Y on each character.
This is where knowing that each item is wrapped in a ListBoxItem container comes in very handy! Since the items panel actually hosts these containers, we just need to modify the container style to bind the Canvas attached properties to the Location properties on the contained item. This can be done by adding the following setters to our style.
<Setter Property="Canvas.Left" Value="{Binding Location.X}" />
<Setter Property="Canvas.Top" Value="{Binding Location.Y} " />
Voîla! Now when we run the code, we see the expected result (now with Lisa selected):
Okay, you might have noticed that I modified a couple of other style properties to provide consistent heights and vertical alignment for the children. The complete sample can be downloaded here.
Common Problems (Revisited)
Remember the common problems we talked about toward the beginning of this article?
- Custom Child Placement
- Mappings between Items and Visuals
- UI Virtualization
- Consistent Item Chrome
- Visible Selection State
We have actually tackled items 1, 4, and 5 already. Our items have custom placement due to bindings on the Canvas attached properties on the item container. We have also defined a custom template within the item container style to give the items a consistent chrome. And finally, we added triggers to that template to show the selected item.
In the next episode, we will talk about how the item container generator can be used to tackle the remaining issues. (No, we will not implement UI virtualization in that article… that will be a separate post later in the series. But we will talk about how the generator enables this virtualization.)
Default Items Hosts and Containers
For your convenience, here is a list of the native ItemsControl classes in WPF, along with their default items hosts and item container types:
ItemsControl Type | Default Items Host | Default Item Container |
ComboBox | StackPanel | ComboBoxItem |
ContextMenu | StackPanel | MenuItem |
HeaderedItemsControl | StackPanel | ContentPresenter |
ItemsControl | StackPanel | ContentPresenter or any UIElement* |
ListBox | VirtualizingStackPanel | ListBoxItem |
ListView | VirtualizingStackPanel | ListViewItem |
Menu | WrapPanel | MenuItem |
MenuItem | StackPanel | MenuItem |
StatusBar | DockPanel | StatusBarItem |
TabControl | TabPanel | TabItem |
ToolBar** | not used | none |
TreeView | StackPanel | TreeViewItem |
TreeViewItem | StackPanel | TreeViewItem |
* If a UIElement is added to the Items collection of an explicit ItemsControl instance (as opposed to an instance of a derived class like ListBox), it will become a direct child of the items panel. If a non-UIElement is added, it will be wrapped within a ContentPresenter.
** Note that I’ve included the ToolBar control in this list because technically, it is an ItemsControl. However, it should be noted that it has certain hardcoded behaviors that diverge from the other ItemsControl classes. It does not wrap its items in an item container and it is hard coded to layout its items in a special ToolBarPanel class. Setting the ItemsPanel property on a ToolBar will not change this behavior. The control template for a ToolBar must include a ToolBarPanel within its visual tree. If the panel is not present, the framework will throw an exception. (Bad form, framework! Shame on you!)
Very nice article again! Keep it up! I hope the Generator part will not take too long, because I have some ideas to chew on for which I will probably need a good understanding of the Generator 🙂
you’re the best… keep it up 🙂
I’m a developer but I fit in the part where I look first on Blend to customize Item Containers and other styles in WPF… But with your article now I understand the basics beyond the process… It’s easy to see why do you call yourself “Doctor” WPF!! Amazing stuff you got here ! 🙂
Very nice.
Binding to Location.X may cause a problem though as Point.X is not observable.
Valid point, Matt. If you were going to modify the X and Y properties independently (rather than simply setting the Location property with a new Point), you would want to bind to an object that implements INotifyPropertyChanged and supplies change notifications for the X and Y properties. 🙂
Obviously I want to avoid adding extra classes so your advice on this would be greatly appreciated; What about memory leaks as described in http://support.microsoft.com/kb/938416? I had a problem with this in one of my larger WPF applications.
Thanks in advance.
Nice posts Doc… I have been able to understand item controls a lot. This post specially helped ands "modifying existing templates" saved lot of time
Thank you
Hi Matt,
The scenario described in that article arises because of a circular rooted reference. This can happen, for example, when a child of a panel contains a binding whose source object is its parent and whose source property is the Children collection of that parent. The problem is that the source property contains a rooted reference back to the child. Normally, binding to the Children collection of a panel would not be a problem, but doing so from one of the actual children will create a problem, as described in the KB article.
The best advice I can give on avoiding that scenario is to bind only to your view model objects, wherever possible. Then the potential for this type of leak won’t even arise. Typically, the only time I use bindings to source objects that are not in the view model is when I’m creating a control style or template. For anything else, I will modify my view model to expose the required properties.
Hope this helps!
-dw
What an excellent post Dr WPF. Thank you for taking the time to write it and for sharing.
Thanks for the post. I’ve been trying to get this working with a databound itemscontrol. Within the itemscontainerstyle definition, I just have a border containing a contentpresenter. I have set the content to “{Binding}” but when i run the app, all i see is the border and not the item that should be hosted by the content presenter. Any thoughts as to where i’m going wrong??
Hi Michael,
It’s hard to say why its not working without seeing a repro. It probably has to do with the data against which you are binding. If its XML data, then try simply including a ContentPresenter without setting the Content property (the framework does funny stuff when binding against an xml source). Otherwise, you will need to debug the binding to see what is happening.
If you have a simple repro, you can send it to me at ask[at]drwpf[dot]com.
Cheers,
-dw
Hello Dr.,
Been reading up your posts for a while now and must mention that all your posts on WPF are exceptional.
Have a question with this one.
“* If a UIElement is added to the Items collection of an explicit ItemsControl instance (as opposed to an instance of a derived class like ListBox), it will become a direct child of the items panel. If a non-UIElement is added, it will be wrapped within a ContentPresenter.”
Does this also apply to UIElements instantiated with datatemplates and bound to the ItemsSource? I was under the impression thats the case but my test shows otherwise and it sucks because I thought it would give a ready to use ItemsSource property on a canvas.
class TestItemVM : DependencyObject
{
string _text = “test”;
public string Text
{
get { return _text; }
set { _text = value; }
}
}
class TestVM : DependencyObject
{
public ObservableCollection Items
{
get
{
ObservableCollection items = new ObservableCollection();
items.Add(new TestItemVM());
return items ;
}
}
}
Hi Nishant,
If you are using a data template, then the item being added to the Items collection is *not* a UI element. In your example, it is your TestItemVM object.
If you create an observable collection of UIElement objects, then those objects will become direct children of the items panel… they will *not* be wrapped in a ContentPresenter. But, in my experience, there is rarely a good reason to maintain your own collection of UIElements. It is much better to go with VM objects.
You can still use a Canvas as the ItemsPanel and use a binding on your ItemContainerStyle to set the appropriate Canvas.Left, Canvas.Top, etc, properties.
Hope this makes sense!
Cheers,
-dw
Thank you Dr.
I realized what it exactly meant after some testing the next day. And I did go down the ItemContainerStyle route (which at the moment is working all right).
The problem is that if you have Path/Line objects mixed with other UIElements , you cannot apply generic Canvas.Left/Canvas.Right logic. I ended up deriving from ListBox and ListBoxItem to handle this. It works but is not pretty. 😀
Thanks again though. I would have never got the hang of this had it not been for your excellent post.
Cheers,
Nishant
What if I wanted to be able to drag these characters around on the canvas and have their Locations change to reflect their position on the canvas?
“There are actually many different ways you can view these styles, as I describe in this forum post. ”
The link is dead.
These articles are great.
#Martin, I just tried the link and it worked for me. Maybe the forums were down earlier. Anyway, let me know if you continue to have trouble with the link and any specifics that might help me repro. Thanks!