ItemsSource for XamDockManager Elements

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.


Comments  (20 )

falbrech
on Thu, Feb 25 2010 11:41 AM

Hi Andrew!

Thanks for that article. Unfortunately I am not able to start the sample project. Am I missing something. I get a "XamlParseException":

Cannot convert the value in attribute 'Converter' to object of type 'System.Windows.Data.IValueConverter'. Das Objekt mit dem Typ Infragistics.Windows.Controls.StringFormatConverter kann nicht in den Typ System.Windows.Data.IValueConverter konvertiert werden.  Error at object 'System.Windows.Data.Binding' in markup file 'TestWindowsApp;component/window_dockbinding.xaml' Line 56 Position 33.

Any idea,

regards,

Florian

[Infragistics] Andrew Smith
on Wed, Mar 3 2010 9:37 AM

Florian, I am not able to reproduce that issue. The sample was set up to use 9.2 so if you change that to a different version make sure that you update all the references in the 2 projects. The converter that you seem to be getting an error on is defined in our Infragistics3.Wpf.v8.2 and later assemblies.

gfroes
on Tue, Jun 22 2010 2:29 PM

Using this approach, if I want a property to be bound to the current active content pane (inside a DocumentContentHost) were would I put it (if possible)?

[Infragistics] Andrew Smith
on Wed, Jun 23 2010 7:27 AM

gfroes, I'm not sure what you mean. If you had an element in the same xaml where you defined the DocumentContentHost, you might set the Name of the DCH and use an ElementName binding (e.g. "{Binding ElementName=dch, Path=ActiveDocument.Header}"). If you wanted to do that from an element within the XamDockManager that may have been created using this method (i.e. not created in xaml) then you would probably go through the xamDockManager (e.g. "{Binding Path=(igDock:XamDockManager.DockManager).Content.ActiveDocument.Header, RelativeSource={RelativeSource Self}}").

jordanhammond
on Thu, Dec 9 2010 4:28 AM

Hi, have just downloaded the source and am getting the following error when trying to run.  Have 9.2 installed.

"Object of type 'Infragistics.Windows.Controls.StringFormatConverter' cannot be converted to type 'System.Windows.Data.IValueConverter'."

"   at System.Windows.Markup.XamlParseException.ThrowException(String message, Exception innerException, Int32 lineNumber, Int32 linePosition, Uri baseUri, XamlObjectIds currentXamlObjectIds, XamlObjectIds contextXamlObjectIds, Type objectType)\r\n   at System.Windows.Markup.XamlParseException.ThrowException(ParserContext parserContext, Int32 lineNumber, Int32 linePosition, String message, Exception innerException)\r\n   at System.Windows.Markup.BamlRecordReader.ThrowExceptionWithLine(String message, Exception innerException)\r\n   at System.Windows.Markup.BamlRecordReader.BaseReadOptimizedMarkupExtension(Object element, Int16 attributeId, PropertyDefinition propertyDefinition, Object value)\r\n   at System.Windows.Markup.BamlRecordReader.ReadPropertyWithExtensionRecord(BamlPropertyWithExtensionRecord bamlPropertyRecord)\r\n   at System.Windows.Markup.BamlRecordReader.ReadRecord(BamlRecord bamlRecord)\r\n   at System.Windows.Markup.BamlRecordReader.Read(Boolean singleRecord)\r\n   at System.Windows.Markup.TreeBuilderBamlTranslator.ParseFragment()\r\n   at System.Windows.Markup.TreeBuilder.Parse()\r\n   at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)\r\n   at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)\r\n   at TestWindowsApp.Window_DockBinding.InitializeComponent() in c:\\Downloads\\DockManagerBindingSample\\TestWindowsApp\\Window_DockBinding.xaml:line 1\r\n   at TestWindowsApp.Window_DockBinding..ctor() in C:\\Downloads\\DockManagerBindingSample\\TestWindowsApp\\Window_DockBinding.xaml.cs:line 28"

[Infragistics] Andrew Smith
on Thu, Dec 9 2010 5:45 PM

Jordan, that's because the sample was using a Binding with our StringFormatConverter which was an IMultiValueConverter. It also implements IValueConverter but maybe you're using an older version before it implemented that interface. You could get the latest hotfix for that version or change the sample to not use that converter.

prinzj
on Wed, Apr 20 2011 5:52 AM

I get no Synchronization with the CurrentItem in the CollectionView when I use your solution. Is it possible to extend it?

[Infragistics] Andrew Smith
on Wed, Apr 20 2011 9:21 AM

Prinzj, no I don't really think that is possible in a generic fashion. You have to remember that this just puts the panes into the target collection (documentcontenthost, tabgrouppane, splitpane) but after that point the user could drag them out to somewhere else.

ingerbjornland
on Thu, Jul 14 2011 5:39 AM

This is just what I needed, but there is a problem concerning saving and loading layout. I want the user to be able to change the layout and save it. This requires that the ContentPanes have unique names so I modified the ContentPaneFactory to name the ContentPanes. The name for a given ContentPane is the same every time it is loaded. I was then able to save the layout, but loading it does not work. When I do, I get twice as many ContentPanes as I had. This is only a problem for my SplitPanes, the DocumentContentHost works well. Have you any experience in using this for saving/loading layout?

I am using the latest version of Infragistics controls, 11.1.

[Infragistics] Andrew Smith
on Thu, Jul 14 2011 3:56 PM

I haven't tried it with saving/loading but since the Name of the SplitPane is set (and should be so the pane isn't removed when the last pane is pulled out) the split pane will get reused when the layout is loaded. When it was pulled out of the tree the ItemsSource of the container factory would have been cleared and so it would have tried to remove the generated panes. But they were already removed as part of the loadlayout prior to the split pane being pulled out and so when the split is reinserted into the layout the ItemsSource of the factory is once again applied and so it tries to create new containers (ContentPane's in this case) for those items and the dockmanager's loadlayout will insert the original ones.

Off the top of my head I can't think of a simple way to deal with this. The SplitPane will be reinserted during the loading of the layout but before the splitpane has access to the xamdockmanager and before the pane has been reinserted. Even if the pane were there the pane factory doesn't expect there to be a container for a given already in the tree so it will still create one.

The only thing that immediately comes to mind is to suppress the ItemsSource from getting cleared and reset. That is happening because the SplitPane comes out of the tree during the loadlayout and therefore its DataContext is no longer inherited. Perhaps you could do a OneTime binding of the DataContext on the SplitPane? E.g. One the splitpane: DataContext="{Binding Mode=OneTime}"

ingerbjornland
on Fri, Jul 15 2011 7:10 AM

Thank you for your answer, I tried your solution but it didn't work. I have implemented some codebehind instead of using binding, and it seems to work fine.

scifi72
on Tue, Oct 4 2011 12:48 AM

We have just encountered exactly the same issue. Could you elaborate on how you overcame this issue?

SumaneshM
on Tue, Oct 18 2011 6:53 AM

Hi,

Is there any way to bind to SelectedItem?

I want to have a reference of the selected tab using this solution.

Any help will be highly appreciated.

Thanks,

Sumanesh

[Infragistics] Andrew Smith
on Tue, Oct 18 2011 10:00 AM

Sumanesh,

That doesn't really work here. XamDockManager allows panes to be moved around - that is why this post is needed in the first place. So attaching to the Selected(Item|Index) of a specific TabGroupPane doesn't make sense because the user may pull a pane out and move it elsewhere (float it, dock it, unpin it, etc.) in which case it will no longer be part of that TabGroupPane. So the SelectedItem would just reflect what is actually the selected item still in the tab group pane (or null if they remove all the panes) - or could be a different pane that the user dragged into the tabgrouppane from somewhere else.

If you want to know what pane is active throughout the dockmanager then you listen to the ActivePaneChanged or if you are using the DocumentContentHost and want to know which is the active document then you can listen to the ActiveDocumentChanged.

SumaneshM
on Tue, Oct 18 2011 9:58 PM

Thanks a lot for your response Andrew, I am able to resolve my issue. I was not actually trying to set the SelectedItem, but wanted to get a reference of the Selected item in the ViewModel. As you have recommended, I now listen to ActivedocumentChanged and update the reference of active document to the ViewModel.

Thanks again.

sunilsourabh1368
on Fri, Dec 23 2011 3:19 AM

Hi Andrew,

I am using Infragistics 4.0. So can you give us complied version of  your solution for Infargistics 4.0 and WPF 4.0. I have trying compiling your project using VS 2010, but having many error because of dll version are different. It would be really helpfull if you can post vs 2010 complied solution using infragistics 4.0 libs.

[Infragistics] Andrew Smith
on Tue, Jan 3 2012 7:54 AM

sunilsourabh1368,

I'm sorry but that really isn't feasible. There are multiple versions of NetAdvantage out there and the code and can be used with any. Also as each SR is released the assemblies would need to be rebuilt. The source code for the post is provided so you can incorporate it however you want. You should be able to update the project to the version of NetAdvantage you are using.

jim geiger
on Sat, Nov 3 2012 8:03 AM

Thanks a lot, this provided a great solution for our application.  Could you demonstrate how to  set other properties through the ItemBinding?  E.g., I was trying to set the relative size of splitter items but could not figure out how to do it.  Thanks again.

[Infragistics] Andrew Smith
on Mon, Nov 5 2012 1:01 PM

Jim, setting/binding an attached property like the SplitPane's RelativeSize really wouldn't be any different. So assuming you modified the sample such that the Person class has a Size property named RelativeSize, the ItemBinding might look like: <igExtensions:ItemBinding TargetProperty="{x:Static igDock:SplitPane.RelativeSizeProperty}" Binding="{Binding Path=RelativeSize, Mode=TwoWay}" />. Note, you'll want to provide a default value for the RelativeSize (e.g. 100,100 is what the property defaults to for SplitPane) if you're going to bind that property.

Also, be aware that RelativeSize is only meaningful when the element is directly within a SplitPane, If you set this on a ContentPane that was within a TabGroupPane that was in a SplitPane then it wouldn't affect the SplitPane's sizing - you would have had to have set that on the TabGroupPane.

Brian Lagunas
on Tue, Sep 24 2013 2:06 PM

If you are using the Infragistics xamDockManager control and using MVVM to compose your views, then you

Add a Comment

Please Login or Register to add a comment.