ItemsSource for XamDockManager Elements

Andrew Smith [Infrag / Tuesday, January 12, 2010

A number of people have asked about binding the ItemsSource of a TabGroupPane to their collection. This is not supported by the TabGroupPane so I thought I would explain why and provide an alternative approach. If you’re looking to bind the DocumentContentHost or even a specific SplitPane to a collection, you should still read on as this solution will allow for that as well.

The TabGroupPane is a derived ItemsControl and the ItemsSource property you see on the TabGroupPane is that of the base ItemsControl class. The ItemsControl is really designed so that whatever containers that are created for items in the ItemsSource are hosted within that ItemsControl – specifically hosted by the Panel indicated by the ItemsPanel property. So items are either automatically pushed into the Children of the ItemsPanel as items are added to the collection or in the case of a VirtualizingPanel, the panel has control over when/which containers are hydrated. Also, once you set the ItemsSource it is not possible to directly change its Items collection – an exception will be thrown by the ItemCollection if you do.

If you think about the TabGroupPane, it needs to be able to manipulate the items it contains – i.e. its Items collection. This can happen for a number of reasons. For example, if the end user pins/unpins a pane, drags a pane into or out of the group, changes the docked/floating state of one of the children, etc. Since that wouldn’t be possible if the control’s items were provided by its ItemsSource (since the Items collection would not be modifiable), the control cannot support binding its Items collection via the ItemsSource.

So how can provide support for this without using the ItemsSource property. If you think about it, the thing that we need is something analogous to the ItemContainerGenerator. This is a class used by the ItemsControl and its associated ItemsPanel to generate containers/elements for items that are not of the desired type. So for a ListBox, ListBoxItem instances are generated for items that are not of that type. Unfortunately the ItemContainerGenerator is not publicly creatable.

So the approach I decided to take was to create my own ItemContainerGenerator type class named ContainerFactoryBase that could be given a source collection and it would maintain an associated collection of containers. Based on the contents of the collection and as the collection raised change notifications it would invoke methods similar to those of the ItemsControl on itself that derived classes could use to create the appropriate container (e.g. IsItemItsOwnContainer and GetContainerForItem) and associate that container with the item (e.g. ApplyItemContainerStyle and PrepareContainerForItem). It would invoke methods as items in the source collection were added/removed/moved so that the derived class could take the appropriate action with the associated container.

I then created a derived class named ContentPaneFactory that creates ContentPane instances as the container for each item and adds it to the associated target. The class exposes several path properties that can be used to bind properties of the ContentPane to properties of the data item including the Header, Content and TabHeader properties. For any other properties, you could either provide a Style for ContentPane using the ContainerStyle property or you could use the ItemBindings collection. Lastly, I also raise an attached event (InitializeContentPane) when a ContentPane is created so you could do any programmatic initialization that might be necessary.

To make this easier to use I created an attached property that would be used to associate the factory with a given target. So you would set the ContentPaneFactory.PaneFactory property to a ContentPaneFactory instance on either a TabGroupPane, SplitPane or DocumentContentHost and that is the target element to which new ContentPane instances are added.

    <igDock:DocumentContentHost >
        <igExtensions:ContentPaneFactory.PaneFactory>
            <!-- Binds to the Documents property of the DataContext. -->
            <igExtensions:ContentPaneFactory 
               ItemsSource="{Binding Documents}"
               HeaderPath="Name"
               ContentPath="." />
        </igExtensions:ContentPaneFactory.PaneFactory>
    </igDock:DocumentContentHost>

You can get the sample here.