DotNetCurry Logo

WPF ItemsControl Fundamentals - Part 1

Posted by: Kent Boogaart , on 7/30/2015, in Category WPF
Views: 22749
Abstract: Explore the fundamental concepts of the WPF ItemsControl class.

A casual glance at WPF’s ItemsControl may not elicit much excitement, but behind its modest façade lies a wealth of power and flexibility. Gaining a deep understanding of the ItemsControl is crucial to your efficacy as a WPF developer. Such an understanding will enable you to recognize and rapidly solve a whole class of UI problems that would otherwise have been debilitating. This two part article will help you obtain this understanding.

As an added bonus, the knowledge you garner here will be applicable to the wider XAML ecosystem. Windows Store, Windows Phone, and Silverlight platforms all include support for ItemsControl . There may be slight differences in feature sets or usage here and there, but for the most part your knowledge will be transferable.

 

In this first part of the article, we will explore the fundamental concepts of the ItemsControl class.

WPF ItemsControl - The Basics

The XAML for the simplest possible ItemsControl is:

<ItemsControl/>

This is equivalent to invoking the ItemsControl constructor and omitting any property modifications. If you put this inside a Window, what you get for your efforts is decidedly dull. Since the ItemsControl has no items (how could it? - we’ve not told it where to get items from) it renders without obvious appearance. It’s still there, but we’ll need to change the Background property to Red in order to reveal it:

<ItemsControl Background="Red"/>
empty-itemscontrol

Figure 1: A default ItemsControl on the left, and with an explicit background color on the right

As you can see in Figure 1, it’s now clear that our ItemsControl occupies the entire Window. This implies that it must be having some effect on the visual tree, and we can confirm this using Visual Studio 2015’s new WPF Visualizer tool. Alternatively, you could use the excellent Snoop utility. See http://snoopwpf.codeplex.com/. In order to use this visualizer we need to be debugging, so we first need to set a breakpoint in the code-behind for our Window. But where? We only have a constructor at this point and during construction WPF has not yet had a chance to realize the visual tree, so we need to add some code. Perhaps the simplest thing to do is add this code to our constructor:

this.Loaded += delegate
{
    var dummy = this;
};

Now we can set a breakpoint on our dummy variable. Execute the application and when the breakpoint is hit, hover your cursor over this. Click the little magnifying glass icon that appears in the tooltip. You will then see the WPF Visualizer, per Figure 2.

wpf-visualizer

Figure 2: The WPF debug visualizer, new to Visual Studio 2015

As you can see, even a default ItemsControl with no items still includes some visual elements. The Border is what we see rendered when we set the BackgrounColor on our ItemsControl . Inside the Border resides an ItemsPresenter and, inside that, a StackPanel. Neither of these elements has any visual appearance themselves – they’re only of utility if our ItemsControl actually has items.

Populating Items

The simplest way to get some items into our ItemsControl is via the Items property. This property is of type ItemCollection, which is essentially just a non-generic collection of items in the ItemsControl . As we’ll discover later, different ItemsControl subclasses have different preferences for the type of items you place within them, but the ItemsControl itself doesn’t care – as long as the item is a FrameworkElement subclass. For example:

<ItemsControl>
    <ItemsControl.Items>
        <Label>A Label</Label>
        <Button>A Button</Button>
        <CheckBox>A CheckBox</CheckBox>
    </ItemsControl.Items>
</ItemsControl>

We can simplify the XAML further because the Items property is the content property for an ItemsControl :

<ItemsControl>
    <Label>A Label</Label>
    <Button>A Button</Button>
    <CheckBox>A CheckBox</CheckBox>
</ItemsControl>

Either way, we get the UI depicted in Figure 3.

ui-items-in-itemscontrol

Figure 3:Some user interface items in an ItemsControl

What if we simply throw some textual content into an ItemsControl instead of UI components? Let’s try throwing some places in there:

<ItemsControl>
    London
    Amsterdam
    Adelaide
</ItemsControl>

Figure 4 shows the result, which is perhaps a little unexpected. What happened here is that any text is automatically placed inside a TextBlock. Per the rules of XML parsing, all three “items” are parsed as one piece of text. We can confirm this via the WPF Visualizer – see Figure 5. If we really wanted each piece of text to be hosted inside a separate TextBlock, we’d need to explicitly state that in our XAML:

<ItemsControl>
    <TextBlock>London</TextBlock>
    <TextBlock>Amsterdam</TextBlock>
    <TextBlock>Adelaide</TextBlock>
</ItemsControl>

text-items-in-itemscontrol

Figure 4:Some textual items in an ItemsControl

wpf-visualizer-for-text-items-in-itemscontrol

Figure 5:Our text has been hosted inside a single TextBlock

OK, so now we know we can add any number of user interface elements to an ItemsControl simply by including them as children of the ItemsControl element in our XAML. But what’s the point? How is this any better than simply including the items as children of a StackPanel instead? In fact, if you look at Figure 5 you’ll see that a StackPanel is hosting our items anyway (we’ll find out why later).

The answer is: you normally wouldn’t. At least, not with an ItemsControl . You might use this approach with subclasses of ItemsControl , for reasons we’ll discover later. Regardless, it’s an instructive stepping-stone on our path to a data-driven ItemsControl , which is where ItemsSource comes in.

The Items and ItemsSource properties are mutually exclusive – it makes sense to set only one of them and any attempt to use both will result in an exception. ItemsSource allows us to give the ItemsControl a data source from which to materialize the items it displays. This could be an XML document or a list of CLR objects. In practice I have found XML document data sources to be of use only in standalone demos, so I am going to ignore them here. In a production system, you will almost certainly want to create view models around your data – whether it’s XML-based or otherwise – and bind your ItemsControl to them instead.

Let’s start out by just assigning a List<string> to the DataContext of our Window:

public MainWindow()
{
    InitializeComponent();

    this.DataContext = new List<string>
    {
        "London",
        "Amsterdam",
        "Adelaide"
    };
}

Now we can modify our XAML thusly:

<ItemsControl ItemsSource="{Binding}"/>

The result is shown in Figure 6. It is visually identical to what we’d get if we manually added three TextBlock controls to the Items property of our ItemsControl . The resulting visual tree is also very similar, but not exactly the same. When using ItemsSource, each of our TextBlock controls is hosted inside a ContentPresenter whereas when using Items they are not. The reasons are not terribly important here, but it comes down to ItemsControl container generation logic, which is responsible for wrapping items in a container if required.

itemscontrol-with-itemssource

Figure 6:Our ItemsControl is now obtaining its data from a List<string>

ItemsControl also provides a HasItems property, but I haven’t found it to be of any use. If you need to trigger UI changes based on your available data, you’re better off modeling those requirements in your view models. Not only does this give you more centralized logic and greater flexibility (for example, what if you need to know when you have only one item?), it also enables you to test such scenarios too.

Now that we know how to get items into our ItemsControl , can we stop ignoring the fact that the items are visually boring? How can we adjust their appearance?

Basic Item Appearance Customization

A simple place to start with adjusting the appearance of our items is with the ItemStringFormat property. This property enables us to provide a format string that will be used to produce the displayed string for each of our items. For example, if we set it as follows:

<ItemsControl
    ItemsSource="{Binding}"
    ItemStringFormat="City: {0}"/>

itemstringformat

Figure 7:Using the ItemStringFormat property to modify the text shown for each item

The result is that each of our items is prefixed with “City: “, as you can see in Figure 7. Of course, all the usual rules and behavior for .NET string formatting apply here. In this case, our data items are of type string, so we’re a little limited in our formatting capabilities. Let’s change to using dates:

this.DataContext = new List<DateTime>
{
    DateTime.Now,
    new DateTime(2013, 02, 13),
    new DateTime(2004, 12, 31)
};

Now we can set our ItemStringFormat as follows:

<ItemsControl
    ItemsSource="{Binding}"
    ItemStringFormat="MMMM dd, yyyy"/>

The result is depicted in Figure 8.

itemstringformat-dates

Figure 8:Binding to DateTime instances without ItemStringFormat (left) and with it (right)

As mentioned earlier, we would typically have view models wrapping the data we wish to bind to. Suppose we want to refactor our list of cities into a list of view models representing those cities. We can achieve this very quickly as follows:

public sealed class CityViewModel
{
    private readonly string name;

    public CityViewModel(string name)
    {
        this.name = name;
    }

    public string Name => this.name;
}

// in our constructor
this.DataContext = new List<CityViewModel>
{
    new CityViewModel("London"),
    new CityViewModel("Amsterdam"),
    new CityViewModel("Adelaide")
};

If we revert our ItemsControl so that it does not specify ItemStringFormat and run the application, we see Figure 9. Clearly this is not what we’re after. What’s happening here is WPF is calling ToString on each of our view models in order to obtain a default representation of them. After all, we haven’t told WPF that we actually want to show the Name property on our view model.

viewmodel-default

Figure 9:Default visualization of our view models

We can do exactly that by specifying the DisplayMemberPath property:

<ItemsControl
    ItemsSource="{Binding}"
    DisplayMemberPath="Name"/>

This gets us back on track and is visually indistinguishable from Figure 6 where we were using a List<string> as our data source. Of course, we can combine DisplayMemberPath with ItemStringFormat:

<ItemsControl
    ItemsSource="{Binding}"
    DisplayMemberPath="Name"
    ItemStringFormat="City: {0}"/> 

This gets us the same UI as shown in Figure 7.

The view model we created above is pretty pointless. All it does is wrap our city name so it’s not adding any value. In reality, we’d likely have several properties for each city:

public sealed class CityViewModel : ReactiveObject
{
    private readonly string name;
    private readonly float population;
    private readonly ObservableAsPropertyHelper<IBitmap> countryFlag;

    public CityViewModel(string name, float population, Task<IBitmap> countryFlag)
    {
        this.name = name;
        this.population = population;
        this.countryFlag = countryFlag
            .ToObservable()
            .ToProperty(this, x => x.CountryFlag);
    }

    public string Name => this.name;

    public float Population => this.population;

    public IBitmap CountryFlag => this.countryFlag.Value;
}

I’ve added population (in millions) and a country flag image. I’m using Splat for the image so that our view model remains agnostic of the platform on which it is running. You’ll notice I’m also deriving from ReactiveObject and using something called ObservableAsPropertyHelper. These are types from ReactiveUI. ReactiveUI is outside the scope of this article, but you can see that our bitmap is loaded asynchronously. I’m using ReactiveUI as a simple means of surfacing the asynchronously-loaded bitmap as a property. Until it has loaded, our CountryFlag returns null. Once loaded, a property changed notification is raised for CountryFlag, and it returns the loaded bitmap.

I then construct the view models in this manner:

new CityViewModel(
    "London",
    8.308f,
    BitmapLoader
        .Current
        .LoadFromResource(
            "pack://application:,,,/ItemsControlArticle;component/Images/gb.png",
            null,
            null))

Again, the details aren’t terribly important for the purposes of this article.

Now that we have view models with more interesting data in them, how can we take advantage of this from our view? The ItemsControl class includes an ItemTemplate property that allows us to specify a rich visual tree to represent each item. Suppose we want to display the city name in bold with the population count underneath it. Off to the right, we want to display the country flag. We can achieve this as follows:

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                    
                <TextBlock
                    Text="{Binding Name}"
                    FontWeight="Bold"
                    FontSize="10pt"/>
                <TextBlock
                    Grid.Row="1"
                    Text="{Binding Population, StringFormat=Population {0:0.#} million}"
                    FontSize="8pt"
                    Foreground="DarkGray"/>
                <Image Grid.Column="1"
                    Grid.RowSpan="2"
                    Source="{Binding CountryFlag, Converter={StaticResource ToNativeConverter}}"/>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

What we’ve effectively done here is told the ItemsControl “hey, whenever you need to render an item, please create a copy of this Grid with these children and these bindings”. The DataContext for each Grid will be a view model, which is why the bindings will work. If we run the application again, we see the UI in Figure 10. It’s far from perfect, but it’s a big step forward.

itemtemplate

Figure 10:Using an ItemTemplate to customize the visual tree of each item

The ItemTemplate gives us a lot of flexibility over how our items are rendered, but ItemsControl offers us even more flexibility by way of its ItemTemplateSelector property. Setting this property to an instance of DataTemplateSelector gives us a means of dynamically selecting a template for each item. Suppose, for example, we generalized our application such that it displays places, not just cities. We can add a CountryViewModel alongside our CityViewModel. Both view models extend a base view model called PlaceViewModel. We would like to display cities differently to countries, but all places are displayed inside the same ItemsControl. This is precisely the kind of scenario that ItemTemplateSelector accommodates.

Some simple refactoring of our existing code gives us our three view models. We can then define a DataTemplateSelector as follows:

public sealed class PlaceDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate CountryDataTemplate { get; set; }

    public DataTemplate CityDataTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is CountryViewModel)
        {
            return CountryDataTemplate;
        }
        else if (item is CityViewModel)
        {
            return CityDataTemplate;
        }

        return null;
    }
}

We’re using a simple type check to determine which DataTemplate to return, where each possible DataTemplate is provided to us via a separate property. We can then define an instance of our PlaceDataTemplateSelector in the resources for our Window:

<local:PlaceDataTemplateSelector x:Key="PlaceDataTemplateSelector">
    <local:PlaceDataTemplateSelector.CountryDataTemplate>
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>

                <TextBlock
                    Text="{Binding Name}"
                    FontWeight="Bold"
                    FontSize="10pt"/>
                <TextBlock
                    Grid.Row="1"
                    Text="{Binding Population, StringFormat=Population {0:0.#} million}"
                    FontSize="8pt"
                    Foreground="DarkGray"/>
                <Image
                    Grid.Column="1"
                    Grid.RowSpan="2"
                    Source="{Binding CountryFlag, Converter={StaticResource ToNativeConverter}}"/>
            </Grid>
        </DataTemplate>
    </local:PlaceDataTemplateSelector.CountryDataTemplate>
    <local:PlaceDataTemplateSelector.CityDataTemplate>
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <TextBlock
                    Text="{Binding Name}"
                    FontWeight="Bold"
                    FontSize="8pt"/>
                <TextBlock
                    Grid.Row="1"
                    Text="{Binding Population, StringFormat=Population {0:0.#} million}"
                    FontSize="8pt"
                    Foreground="DarkGray"/>
            </Grid>
        </DataTemplate>
    </local:PlaceDataTemplateSelector.CityDataTemplate>
</local:PlaceDataTemplateSelector>

We could also inline the definition within our ItemsControl, but I usually find it cleaner to separate any relatively complex elements out into the resources section, or even other files altogether. Regardless, we can now modify our ItemsControl thusly:

<ItemsControl
    ItemsSource="{Binding}"
    ItemTemplateSelector="{StaticResource PlaceDataTemplateSelector}"/>

The end result is shown in 1. As you can see, we’re only showing the flag now if the item represents a country. In addition, the font size for the country name is larger than the font size for city names.

itemtemplateselector

Figure 11: Using ItemTemplateSelector to vary the visuals on a per-item basis

Of course, the flexibility that we get from ItemTemplateSelector does not end there. We could have it choose from any number of templates based on any programmable factor we desired. That said, the use of ItemTemplate is far more common than ItemTemplateSelector . Most of the time you will know at design-time what the item should look like, and there’s rarely a need to vary that appearance dynamically.

Conclusion

You should now have a firm grasp on ItemsControlfundamentals – how to declare one in XAML, how to populate it with data, and how to customize the appearance of items within it. For some simple scenarios, this is all the knowledge you need to create a solution. However, there is much and more to learn beyond what we’ve covered in this first part of the article. In part 2 of this article, we’ll dig much deeper and acquire the necessary skills to utilize ItemsControl in advanced scenarios.

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!