Binding DocumentPane.ItemsSource to an ObservableCollection

Jul 23, 2008 at 1:44 PM
Hi,
I am struggling to find a way how I can bind DocumentPane.ItemsSource to an ObservableCollection to display for example a list of projects, each of which is a tab but haven't succeeded yet.
Do you have any hints on this?

Thanks.
Jul 25, 2008 at 10:15 AM
The right implementation can vary a lot depending on your application requirements, anyway I would code something like this:

Assuming a class describing a project:
public class Project : MyGeneralDocument
{
    public Project(string prjName, string prjPath) : base(prjName)
    {
            Path = prjPath;
            //base.Name == prjName
    }
}

Create a derived DocumentContent:

public class MyDocumentContent : DocumentContent
{
    public class MyDocumentContent(MyGeneralDocument doc)
    {
        Title = doc.Name;
    }
}

public class MyProjectDocumentContent : MyDocumentContent
{
    public class MyProjectDocumentContent(Project prj) : base(prj)
    {
        Icon = "ProjectIcon";
        Title = string.Format("[{0}]{1}", prj.Name, prj.Path);
    }
}

Then you can create an observable collection of MyDocumentContent:
ObservableCollection<MyDocumentContent> _listOfAllTheDocuments = new ObservableCollection <MyDocumentContent>();
public ObservableCollection<MyDocumentContent> Documents
{
    get
    {    return listOfAllTheDocuments;}
}

...and set the items source (assuming that the right datacontext is set before) :
<DocumentPane ItemsSource={Binding Path=Documents}/>

Consider that I had no time to build it, but it should be a pratical way to achive what you need.

As last hint I can suggest you is to implement a Model-View-Controller pattern (you can find a lot of clarifying articles from Josh Smith).
Using this pattern you can abstract away the WPF related stuff like the DocumentContent object as well the icon used for the project document from the model that in your case
is the class Project.

Hope to be helpful!
Jul 28, 2008 at 8:31 AM
Hi adospace,
thanks for your reply. In fact, I already tried the similar way as you suggested above but somehow strangely, it didn't work. I always received the following exception  "ManagedContent must be put under a DockingManager!"

Here is the code:

 

<DockPanel LastChildFill="True" Grid.Row="1">

 

 

<ContentControl x:Name="TestContainer">

 

 

<ad:DockingManager>

 

 

<ad:ResizingPanel Orientation="Horizontal">

 

 

 

<ad:DocumentPane x:Name="dockingHost" ItemsSource="{Binding OpenProjectListViewModel.Documents}}">

 

 

<ad:DockablePane>

 

 

<ad:DockableContent DataContext="{Binding Source={StaticResource MainViewModelDataContext}, Path=DataContext.ProjectSelectorViewModel}" Title="Project selector" IconSource="/Images/star.png">

 

 

<ContentControl Content="{Binding}"/>

 

 

</ad:DockableContent>

 

 

</ad:DockablePane>

 

 

 

</ad:ResizingPanel>

 

 

</ad:DockingManager>

 

 

</ContentControl>

 

 

</DockPanel>

 

The bound Documents:

///

<summary>

 

 

/// List of documents to be display as AvalonDock document

 

 

/// </summary>

 

 

public ObservableCollection<MyProjectDocumentContent> Documents

 

{

 

get; private set;

 

}


And the MyProjectDocumentContent class:

 

/// <summary>

 

 

/// This class represents a document displaying a project in AvalonDock tab

 

 

/// </summary>

 

 

public class MyProjectDocumentContent: DocumentContent

 

{

 

/// <summary>

 

 

/// Constructor

 

 

/// </summary>

 

 

/// <param name="projectViewModel"></param>

 

 

public MyProjectDocumentContent(ProjectViewModel projectViewModel)

 

{

Title = projectViewModel.Title;

}

}



Do you have any idea?
Jul 28, 2008 at 9:02 AM
Hi, I think that it's a bug. Try to comment out lines 59-60 from file ManagedContent.cs. Let me know thanks!
Jul 28, 2008 at 9:36 AM
Hi adospace,
I've done like your suggestion and it looks better now :). I can see the project titles displayed correctly in the tab.
However, the content of the documents are all empty. It seems that the DataTemplate is not applied correctly.
I will keep you posted.

 

Jul 28, 2008 at 11:36 AM
The datatemplate shows SelectedItem.Content, so it shows the content of MyProjectDocumentContent. How do you set MyProjectDocumentContent contents?
Jul 29, 2008 at 9:19 AM
Edited Jul 29, 2008 at 9:48 AM
Thanks for your feedback.

My first attempt was to set MyProjectDocumentContent.Content was in its constructor:

public

MyProjectDocumentContent(ProjectViewModel projectViewModel)

 

{
Title = projectViewModel.Title;

 

Content = projectViewModel;

 

//var win = Application.Current.MainWindow;

 

 

 

//ContentTemplate = win.FindResource("CompleteProjectTempl") as DataTemplate;

 

 

}

I was hoping that the DocumentPane behave in the same way like WPF TabControl so I tried to bind the DocumentPane's ItemsSource to ab ObservableCollection as said above and SelectedItem to an ActionDocument of type MyProjectDocumentContent property in the ViewModel (I am using MVVM pattern here) that keeps track the currently selected project in tab.

Here is what I want to archieve:

<ad:DockingManager>

 

 

 

 

<ad:ResizingPanel Orientation="Horizontal">

 

 

 

<ad:DocumentPane SelectedItem="{Binding Path=OpenProjectListViewModel.ActiveDocument, Mode=TwoWay}"

 

 

 

ItemsSource="{Binding Path=OpenProjectListViewModel.Documents}">

 

 

 

</ad:DocumentPane>

 

 

 

 

<ad:DockablePane>

 

 

 

<ad:DockableContent DataContext="{Binding Source={StaticResource MainViewModelDataContext}, Path=DataContext.ProjectSelectorViewModel}" Title="Project selector" IconSource="/Images/star.png">

 

 

 

<ContentControl Content="{Binding}"/>

 

 

 

</ad:DockableContent>

 

 

 

</ad:DockablePane>

 

 

 

 

</ad:ResizingPanel>

 

 

 

 

</ad:DockingManager>

 

 


and in ViewModel

............

 

public MyProjectDocumentContent ActiveDocument

 

{

 

 

get { return _activeDocument; }

 

 

 

set

 

 

{

 

if (ActiveDocument == value)

 

 

 

return;

 

_activeDocument =

 

value;

 

OnRaisePropertyChanged(

 

"ActiveDocument");

 

}

}

 

 

/// <summary>

 

 

 

/// List of documents to be display as AvalonDock document

 

 

 

/// </summary>

 

 

 

public ObservableCollection<MyProjectDocumentContent> Documents

 

{

 

 

get; private set;

 

}

 

.........

This time, I experience another strange behavior. Whenever I click the left mouse on a tabbed project, hoping that the tab will switch to that project but the project is not switched. On the contrary, more than one projects in the tab are marked as active.

Jul 29, 2008 at 9:41 AM
One of my suspicions is this method in the Pane class but I am still not sure yet

 

protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)

 

{

HasSingleItem = (Items.Count == 1);

 

if (Items.Count > 0)

 

{

 

int currentIndex = SelectedIndex;

 

SelectedIndex = -1;

 

if (currentIndex < 0 ||

 

currentIndex >= Items.Count)

currentIndex = Items.Count - 1;

SelectedIndex = currentIndex;

}

 

//else

 

 

// RemoveDragPaneReferences();

 

 

base.OnItemsChanged(e);

 

}

Jul 29, 2008 at 10:18 AM
I guess I've found something :


In class ManagedContent:

protected

override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)

 

{

 

base.OnPropertyChanged(e);

 

 

 

if (e.Property == IsSelectedProperty && (bool)e.NewValue)

 

{

 

if (ContainerPane != null)

 

{

 

//ContainerPane.ActiveContent = this;

 

ContainerPane.SelectedItem =

this;

 

}

}

}

while debugging using breakpoints I noticed that whenever I left click to choose a project in the tab, this method is called but ContainerPane ALWAYS returns a NULL which prevent the code line ContainerPane.SelectedItem = this;  from being excecuted.

So the question now is: why is ContainerPane (Parent of the ManagedContent) NULL?

Jul 29, 2008 at 11:03 AM

I triedy to search for the Pane parent in the visual tree instead of relying on the Parent property that has to do with the logical tree like this and it works.

if

(ContainerPane == null)

 

{

 

var parent = Utilities.FindAncestor(typeof (Pane), this, true) as Pane;

 

parent.SelectedItem =

this;

 

}

 

 
And here is FindAncestor method:

 

 

public static FrameworkElement FindAncestor(Type ancestorType, Visual visual, bool fromCurrent)

 

{

 

if (!fromCurrent)

 

visual = (

Visual)VisualTreeHelper.GetParent(visual);

 

 

while (visual != null && !ancestorType.IsInstanceOfType(visual))

 

{

visual = (

Visual)VisualTreeHelper.GetParent(visual);

 

}

 

return visual as FrameworkElement;

 

}

Nov 17, 2008 at 3:02 PM
Edited Nov 17, 2008 at 3:05 PM
Hi !

I encounter the same problem as minhquang104, that is to say I try to bind my DocumentPane's ItemSource to an ObservableCollection of custom objects. I use a DataTemplate to display my custom objects and my DocumentPane displays the right number of tabs, but they are all empty.

Here is a summary of my code :
1) my window code


    <Window.Resources>
        <CollectionViewSource x:Key="powerSupplyDataView"
                              Source="{Binding Source={x:Static Application.Current}, Path=PowerSupplyCollection}" />
    </Window.Resources>

[...]

        <ad:DockingManager x:Name="dockingManager">
            <ad:ResizingPanel Orientation="Vertical">
                <ad:ResizingPanel Orientation="Horizontal">
                    <ad:DocumentPane x:Name="documentsHost"
                                     ItemsSource="{Binding Source={StaticResource powerSupplyDataView}}">
                    </ad:DocumentPane>
                </ad:ResizingPanel>
            </ad:ResizingPanel>
        </ad:DockingManager>


2) my DataTemplate code:


<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock">



    <DataTemplate DataType="{x:Type model:PowerSupply}">

        <!--ad:DocumentContent>
            
            <ad:DocumentContent.Content-->

                <Grid x:Name="tabItemPowerSupplyGrid"
                      ShowGridLines="False">

                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>


                    <ScrollViewer x:Name="scrollViewer"
                                  HorizontalScrollBarVisibility="Auto"
                                  VerticalScrollBarVisibility="Auto"
                                  HorizontalAlignment="Stretch"
                                  HorizontalContentAlignment="Stretch"
                                  VerticalAlignment="Stretch"
                                  VerticalContentAlignment="Stretch"
                                  Grid.Row="1">

                        <!-- Panel containing the data -->
                        <DockPanel x:Name="dockPanelData"
                                   HorizontalAlignment="Stretch"
                                   VerticalAlignment="Stretch"
                                   Margin="30,10,30,10">

[...]

                        </DockPanel>

                    </ScrollViewer>

                </Grid>
                
            <!--/ad:DocumentContent.Content>
            
        </ad:DocumentContent-->

    </DataTemplate>

</ResourceDictionary>


3) my custom objects list:


        public ObservableCollection<PowerSupply> PowerSupplyCollection
        {
            get { return this.m_powerSupplyCollection; }
            set { this.m_powerSupplyCollection = value; }
        }


I can't understand why my tabs are empty.
Does anyone see what's wrong with my code ?

Thanks a lot!











Oct 13, 2010 at 8:32 AM

Hi!

I have a similar issue, as I'm using a tabcontrol to display a set of documents of a custom type in an observableCollection called OpenedDocuments:

<TabControl ItemsSource="{Binding Path=OpenedDocuments}">
    <TabControl.Resources>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="HeaderTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <ContentPresenter Content="{Binding Path=Name}"></ContentPresenter>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <avalonEdit:TextEditor Document="{Binding Path=TextDocument}"></avalonEdit:TextEditor>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </TabControl.Resources>
</TabControl>

I would like to be able to switch form tabcontrol to DocumentPane. According to all the documentation I found, a DocumentPane can only support its own content (DocumentContent, etc), so it seems to me that the only way to do that is to change my custom item type so that it derives from DocumentContent. Is there a way to perform the same thing without having to change my implementation?

What I would like is the DocumentPane to be able to automatically generate its own item (e.g.: TabItems), populating them with the objects provided by the itemsSource.

Is it possible?

 

Thanks!