XPF Control Tour: ItemsControl

Updated 2010-10-20: Altered BindingFactory.CreateOneWay<SolidColorBrush> to CreateOneWay<Brush> - co/contravariance isn’t supported on WP7.  ItemTemplate now passes DataContext as an argument of the Func (Build 37+).

As XPF matures the number of controls available increases, but it’s largely down to the early adopter to seek them out and figure out how to use them.  Our thinking here is that most developers will have had some WPF/Silverlight experience so the usage pattern will be easy to work out.  That may have been a little presumptuous on our part, so in a series of short and sharp posts we’ll take a tour through some of the less obvious controls.

First up the ItemsControl, one of my favourites.  In XPF if you want to repeat a block of UI for n number of items, you invariably want an ItemsControl.  Think ListBox, ComboBox or maybe items in a menu.

Let’s look at a complete example and it’s output:

public class MyComponent : DrawableGameComponent{
    private RootElement rootElement;

    public MyComponent(Game game)
        : base(game)
    {
    }

    public override void Draw(GameTime gameTime)
    {
        this.rootElement.Draw();
        base.Draw(gameTime);
    }

    public override void Update(GameTime gameTime)
    {
        this.rootElement.Update();
        base.Update(gameTime);
    }

    protected override void LoadContent()
    {
        var spriteBatchAdapter = new SpriteBatchAdapter(new SpriteBatch(this.GraphicsDevice));
        var primitivesService = new PrimitivesService(this.GraphicsDevice);
        var renderer = new Renderer(spriteBatchAdapter, primitivesService);

        this.rootElement = new RootElement(this.GraphicsDevice.Viewport.ToRect(), renderer);

        var colors = new List<SolidColorBrush>
            {
                new SolidColorBrush(Colors.Red), 
                new SolidColorBrush(Colors.Blue), 
                new SolidColorBrush(Colors.Yellow)
            };

        var itemsControl = new ItemsControl
            {
                ItemsSource = colors, 
                ItemTemplate = dataContext =>
                    new Border
                        {
                            Width = 50, 
                            Height = 50, 
                            Margin = new Thickness(10), 
                            Background = new SolidColorBrush(Colors.Gray)
                        }
            };

        this.rootElement.Content = itemsControl;
    }
}

image

That’s all standard XPF scaffolding code right up until we define a list of colors and define an ItemsControl.  Things to note:  ItemsSource is set to the list of colors (you can use anything that implements IEnumerable).  The ItemTemplate property is a Func<IElement> (all controls in XPF implement IElement) and is executed for each item specified in ItemsSource, hence we defined 3 colors in our list and we got 3 borders rendered.

The DataContext & Data Binding

At the moment each Border background is gray.  How do we get it to use the colors in our list?  Each item that the ItemsControl generates will have it’s DataContext property set to it’s associated item in the ItemsSource.  Using XFP’s data binding, we can bind to that DataContext property in our ItemTemplate and dynamically assign the background color of each Border:

var itemsControl = new ItemsControl
{
    ItemsSource = colors,
    ItemTemplate = dataContext =>
    {
        var border = new Border
        {
            Width = 50,
            Height = 50,
            Margin = new Thickness(10)
        };

        border.Bind(
            Border.BackgroundProperty,
            BindingFactory.CreateOneWay<Brush>());

        return border;
    }
};

image

Whilst the data context of each item is automatically set by XPF to support various binding scenarios, you’ll also note that the data context is passed as an argument of the ItemTemplate function.  This facilitates run-time decisions about the template that should be returned.

If you’re interested in all the Data Binding options, be sure to check out our Inside XPF: Data Binding post.

ItemsPanel

The way in which the ItemsControl lays out it’s children can be configured via it’s ItemsPanel property.  By default the ItemsControl will use a StackPanel (which itself defaults to an orientation of vertical).  But you can change the ItemsControl to use anything that inherits from Panel, meaning you can apply your own layout logic.  Custom panels is a subject in-upon itself which we’ll cover in a future post, but the process is almost identical to creating a custom panel in WPF/Silverlight, so generally speaking any WPF/Silverlight panel implementation you can find, will work in XPF.

For now, let’s change our ItemsPanel to a horizontal StackPanel:

var itemsControl = new ItemsControl
{
    ItemsPanel = new StackPanel { Orientation = Orientation.Horizontal },
    ItemsSource = colors,
    ItemTemplate = dataContext =>
    {
        var border = new Border
        {
            Width = 50,
            Height = 50,
            Margin = new Thickness(10)
        };

        border.Bind(
            Border.BackgroundProperty,
            BindingFactory.CreateOneWay<Brush>());

        return border;
    }
};

image

 

Observable Collections

In our example so far we’ve been binding our ItemsSource to a List<T> which means after creation our ItemsControl is pretty much not going to change.  If however you set the ItemsSource to a collection that implements INotifyCollectionChanged (such as the commonly used ObservableCollection<T>) the ItemsControl will automatically hook onto CollectionChanged event and dynamically, update it’s children.  All actions are supported meaning you can add, remove, replace or move items in the bound collection and the ItemsControl will automatically update it’s layout to reflect the changes.

Using the ItemsControl with an observable collection and data binding is another great example of XPF helping you separate your presentation and implementation concerns.

David Wynne
More from David