(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

ItemsControl from scratch to your own controls

(10 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   76
Comments:   9
More Articles
3 comments   /   posted on May 16, 2011
Tags:   itemscontrol , custom-controls , andrea-boschin
Categories:   Controls

The ItemsControl is a kind of strange presence when you are working in Silverlight.  As I've already said in a previous article, the ItemsControl is one of the three kind of controls you can meet in Silverlight, at the root of a big category of controls that are able to display lists of items. Differently from other base classes it works also if you use it as-is, but many important controls directly or indirectly inherit from it. ListBox, DataGrid, and many other beyond suspicion get their nature from the ItemsControl so, having a deep knowledge of it is really important if you want to improve your manufacturing of controls but also to use it properly into your applications.

Download the source code

Using the ItemsControl

At the basis of the ItemsControl there is a really simple concept. Imagine to have an area where you can attach an indefinite number of children and they are displayed inside a given layout. These children may be equal, similar or also completely different from each other and the ItemsControl will take them in the order of appearance and it will put them into the chosen layout. Here is a short snippet that shows what I'm saying:

<ItemsControl>
    <ItemsControl.Items>
        <Button Content="First" />
        <Rectangle Width="20" Height="20" />
        <Button Content="Second" />
        <Rectangle Width="20" Height="20" />
        <Button Content="Third" />
    </ItemsControl.Items>
</ItemsControl>

The default layout defined for the ItemsControl is a StackPanel so the three buttons in the snippet will be displayed one above the other, separated by a rectangle. It is very common to have a vertical list of items in the page and most of the time it suffice but in other cases you can have the need to display the items in a completely different layout. The default layout can be changed easily using the ItemsPanel property and and ItemsPanelTemplate:

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <toolkit:WrapPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.Items>
        <Button Content="First" />
        <Rectangle Width="20" Height="20" />
        <Button Content="Second" />
        <Rectangle Width="20" Height="20" />
        <Button Content="Third" />
    </ItemsControl.Items>
</ItemsControl>

In this snippet I used a WrapPanel as layout so the items will be organized like the files in a folder.  The customization of the layout is for sure one of the most beautiful features of the ItemsControls.

Adding elements to the Items collection is one way of using this ItemsControl but it is not the way the control is usually used. It may be useful sometimes - especially when you are building composable layouts - but the most of the times you will specify a DataTemplate and you will use the data binding to repeat a number of times the same fragment of code. People experienced in ASP.NET will find a strict analogy with the Repeater control. When you operate this way you create the DataTemplate - in place or into a resources section - and then you connect a data source to the ItemsSource property. Here is a snippet:

<ItemsControl ItemsSource="{Binding Path=Names}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Mixing the power of the data binding with the simplicity of the ItemsControl give you great control on the output you want for your application and definitely there is not anything you can't do with this beautiful couple. Obviously there is cases where the price to pay to get something working is too much and the ItemsControl become a possible way but not the most convenient. This is the reason why many controls implements a specific logic like the ListBox, the ComboBox and the DataGrid, but you have always to remember that at the very base of them there is the ItemsControl working and many of the thing you can do with it are still possible with high level controls.

Getting access to generated elements

It may sounds silly, but once you have populated an ItemsControl with databinding it is not so simple to get access to the elements generated by this operation. It is true for sure that in a well designed application you shouldn't have the need of accessing a generated UI element because the access to the source collection should suffice thanks to the benefits of the data binding, but as usual there is always a slight possibility you have to access the UI elements.

When the databinding happen the ItemsControl takes the items of the collection in the ItemsSource and for every one it generates a container. In the basic version of the ItemsControl the container is a ContentPresenter but usually every kind of control inheriting from ItemsControl can specify its own container. So the DataTemplate is paired with the container  and it generates the UI inside of it. Unfortunately if you try to access the Items property after the databinding you will find that the result is the datasource itself and not the generated elements.

For the purpose of working around to this problem they exist a bunch of methods exposed by the ItemContainerGenerator. Here is the methods with a brief description:

ContainerFromIndex Gets the ContentPresenter (or other container with inherited controls) at the specified index.
ContainerFromItem Gets the ContentPresenter (or other container with inherited controls) that is generated by the specified item in the Data Source.
IndexFromContainer Gets the index of the specified container
ItemFromContainer Get the item that has generated the specified container

Given these method and pairing them with the VisualTreeHelper you can solve almost every need you may have. As an example, if you want to access the TextBlock generated by an item in the previous snippet you have simply to write the following code and as a result you will see the name "Gaia" becoming bold

DependencyObject container = names.ItemContainerGenerator.ContainerFromItem("Gaia");
TextBlock tb = (TextBlock)VisualTreeHelper.GetChild(container, 0);
tb.FontWeight = FontWeights.ExtraBold;

One step forward: creating your own ItemsControl

Now that we have undisclosed the basic secrets of the ItemsControl the next question is for sure: "how can I create my own custom ItemsControls"? Silverlight offers a huge number of controls, most of them are specialized version of the ItemsControl but one time or the other you will have the need to implement your control as you usually do with the Control and ContentControl class. Implementing a custom ItemsControl is not so easy almost until when you do not have fully understand how it works.

The main problem with ItemsControl is that you not only have to provide your own template as you do with the other controls but you also have to implement a basic logic overriding a bunch of method that are called by the runtime when items are added or removed from the control, no matter if this happen with databinding or adding manually elements to the Items collection.

But starting from scratch, for the purpose of this article we will extend the example of the article on ContentControls implementing a BalloonList control that is able to show a list of Balloons like a chat. As usual the first step is to provide a template:

<Style TargetType="local:BalloonList">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:BalloonList">
                <Border Width="{TemplateBinding Width}" 
                        Height="{TemplateBinding Height}" 
                        Padding="{TemplateBinding Padding}"
                        HorizontalAlignment="{TemplateBinding HorizontalAlignment}" 
                        VerticalAlignment="{TemplateBinding VerticalAlignment}"
                        BorderBrush="{TemplateBinding BorderBrush}" 
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <!-- here goes the items -->
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This template is really simple. It is a border that wraps the items of the ItemsControl. Now we have also to create the class that represent the control and it have to inherits from ItemsControls:

public class BalloonList : ItemsControl
{
    public BalloonList()
    {
        this.DefaultStyleKey = typeof(BalloonList);
    }
}

Until here there is not any difference from other types of controls. The next step is to specify where we want the items would be added inside the template. For this purpose it exists an ItemsPresenter class that - like the ContentPresenter - is able to work as a placeholder in the XAML with the sole difference that an ItemsControl can have only one ItemsPresenter. As far as I know you can also specify a number of presenters in the markup but only the first will get the items and the others will remain unchanged.

<Style TargetType="local:BalloonList">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:BalloonList">
                <Border Width="{TemplateBinding Width}" 
                        Height="{TemplateBinding Height}" 
                        Padding="{TemplateBinding Padding}"
                        HorizontalAlignment="{TemplateBinding HorizontalAlignment}" 
                        VerticalAlignment="{TemplateBinding VerticalAlignment}"
                        BorderBrush="{TemplateBinding BorderBrush}" 
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ItemsPresenter />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now that the presenter is in place if you try to put the control in the markup and run the project you will see that it behaves exactly as a basic ItemsControl. Also if the control works fine it is unuseful just because we have to customize how the items are added and removed in the collection. There is a number of method that participate in this work and we will meet each one step by step.

The first thing is to decide what will be the container of the generated UI. In my case it will be the Balloon control. It is important the item you use is a ContentControl so you can seamless support both databinding and manual items. In case of manual add the Content property will contain the added item but in case of databinding the Content property will contain the item got from the datasource and the ContentTemplate will refer to the DataTemplate provided for the binding. Once we decided, we have to override the IsItemItsOwnContainerOverride. The purpose of the method is handling the case when someone add to the Items collection and instance of the container we choosed. In this case the method returns true and the logic must use the added container instead of creating an instance.

protected override bool IsItemItsOwnContainerOverride(object item)
{
    return item is Balloon;
}

After this method we have to provide the logic to create and initialize the container if it was not provider by the user. For this purpose we override the GetContainerForItemOverride method. This method has no parameters but have to return an instance of the container:

protected override DependencyObject GetContainerForItemOverride()
{
    Balloon balloon = new Balloon();
 
    if (this.ItemsContainerStyle != null)
        balloon.Style = this.ItemsContainerStyle;
 
    return balloon;
}

In this call I also initialized the style of the container using a dependency property I've created. The ItemsContainerStyle will be assigned to each container generated (one for every item) and will give the user the ability to customize the aspect of the container itself.

Finally we are called to join the generated container with the data item. So we have to override the PrepareContainerForItemOverride() method. I would remember that the Balloon control has two Content property (Content and HeaderContent). In this case the runtime will automatically connect Content and ContentTemplate of the container with the item but we have to manually make the connection for HeaderContent and HeaderTemplate properties:

protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
    base.PrepareContainerForItemOverride(element, item);
 
    Balloon balloon = element as Balloon;
 
    if (!Object.ReferenceEquals(element, item) && balloon != null)
    {
        if (this.HeaderTemplate != null)
        {
            balloon.HeaderContent = item;
            balloon.HeaderTemplate = this.HeaderTemplate;
        }
    }
}

After leaving the base implementation making its work we take an hard reference to the element ensuring it is a Balloon (so it is true we have created it but making the check is a sort of defensive programming). Then we check that the element and the item are not the same instance. In this case we have nothing to do because the item has been added to the Items collection so databinding will have no effects.

After passed this check we also check for the presence of an HeaderTemplate and finally we put the data item in the HeaderContent property and the template in the HeaderTemplate property of the Balloon. This suffice and after this running a code with the BalloonList will magically show the items decorated by our own container.

The figure on the side is the output of the example I attached to this article. Please be aware that the output I show here imply you work providing an Item and Header DataTemplate and an ItemsContainerStyle that give the style to the orange Balloon. The item DataTemplate will put in place the text into the balloon and the HeaderTemplate will populate the Image and username in the header.

We have still not completed our work. The control seem to work fine but we have to give an implementation to a final method that is responsible of clearing resources once you remove an item from the control.

The method ClearContainerForItemOverride may sounds unuseful and at the first sight we may be tempted to spare some work and leave it void. Indeed it is very important that we give an implementation to free references that may cause memory leaks and someway break the connection we made between the data item and the container. In our case we put a reference to the item in the balloon header and content so we have to clear these references. Also if we had hooked up some events it would be important to clear also every delegate so we give to the Garbage Collector the opportunity of freeing resources that may be useful to other parts of the applications.

protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
    base.ClearContainerForItemOverride(element, item);
 
    Balloon balloon = (Balloon)element;
 
    if (!balloon.Equals(item))
    {
        balloon.ClearValue(Balloon.ContentProperty);
        balloon.ClearValue(Balloon.ContentTemplateProperty);
        balloon.ClearValue(Balloon.HeaderContentProperty);
        balloon.ClearValue(Balloon.HeaderTemplateProperty);
    }
}

In the code we manage both the Content and HeaderContent together with ContentTemplate and HeaderTemplate just to be double sure we have correcty broken every possible reference.

The beauty brought to the web

I think there is not any doubt about the power of the ItemsControl. I meet it for the first time working with the first releases of WPF and I immediately fallen in love with its simplicity and flexibility. Used as-is or inheriting your own custom control is so easy and require very few code. I will never end to thank Silverlight to brought this beauty to the web.

About the Author

Andrea BoschinAndrea Boschin is 41 years old from Italy and currently lives and works in Treviso, a beautiful town near Venice. He started to work in the IT relatively late after doing some various jobs like graphic designer and school teacher. Finally he started to work into the web and learned by himself to program in VB and ASP and later in C# and ASP.NET. Since the start of his work, Andrea found he likes to learn new technologies and take them into the real world. This happened with ASP.NET, the source of his first two MVP awards, and recently with Silverlight, that he started to use from the v1.0 in some real projects.
 


Subscribe

Comments

  • avrashow

    Re: ItemsControl from scratch to your own controls


    posted by avrashow on Jun 11, 2011 02:15
    Great article. Shows limitations and how to work around them. Good simple examples. If you want some minor English grammar wording corrections and suggestions, for this article or future articles, email me at my name dot hotmail dot com. Grazie!
  • AndreaBoschin

    Re: ItemsControl from scratch to your own controls


    posted by AndreaBoschin on Jun 11, 2011 10:45
    Hi, thanks for your comment. I'm very interested in your suggestions. Please use the contact form on my blog for sending me a message: http://www.silverlightplayground.org/contact.aspx
  • desire2learn

    Re: ItemsControl from scratch to your own controls


    posted by desire2learn on Feb 15, 2012 16:15

    Hi ,really a nice article,and thankful to you,

    i am trying to develop a custom items control like chessboard ,each cell will behave as a storage unit and chessboard will behave like a storagebox ,storagebox can have different storage units ,and each storage unit is recognised by its row and column intersection ,can you please guide me i already have a custom storageunit control.i would be greatful to you if you able to guide.

    just guide me where should is start..

    with best regards shabbir

Add Comment

Login to comment:
  *      *