DocumentWindow and MVVM pattern

Docking/MDI for WPF Forum

Posted 9 years ago by Aled Hughes - Principal Software Engineer, Control Techniques Ltd.
Avatar
Hi,
I came across some unexpected behaviour in implementing a tabbed mdi client app that perhaps you could shed some light on:

In the app, the object passed into the "Content" parameter of the DocumentWindow constructor is actually an instance of a ViewModel class (so no association with UI) and there's a DataTemplate entry in the main application resource dictionary which maps that VM to an actual view class which will derive from FrameworkElement.

Now, say I have two separate instances of the ViewModel class created programmatically, each holding different data and I want to create two document windows in the MDI client, one for each VM. I initially thought that an instance of the View class would get created for each VM (i.e. each doc window tab) but that isn't the case - only one instance is created and the DataContext is changed whenever I switch between the tabs. Because the view holds some local data itself, this behaviour causes some issues.

I worked around the issue by wrapping the VM up in a new ContentControl prior to passing it into the DocumentWindow constructor, and then a unique view instance is created for each tab. However, I'm rather intrigued as to why it was behaving in that way.

Thanks,
aled.

Comments (13)

Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Aled,

The docking window containers (like tabbed MDI) behave similar to a standard TabControl. When a tab is selected, the related content is rendered in a ContentPresenter that fills the content area. When another tab is selected, the ContentPresenter is updated to show the data for the other tab, and the previous content is removed from the visual tree.


Actipro Software Support

Posted 9 years ago by Michael Per
Avatar
Aled,

How did you bind the collection of ViewModels to the MDI? I'm tried the code below, but I'm getting the error that "Type 'System.Windows.Controls.ItemsControl' is not allowed to be an item of DockingWindowContainer."

ActiPro, perhaps you can point me to the right direction as well?

    <DataTemplate x:Key="WorkspaceTemplate">
        <docking:DocumentWindow Title="{Binding DisplayName}">
            <ContentControl Content="{Binding}"/>
        </docking:DocumentWindow>
    </DataTemplate>

    <DataTemplate x:Key="WorkspacesTemplate">
        <docking:DockSite CanDocumentWindowsRaft="True">
            <docking:Workspace>
                <docking:TabbedMdiHost>
                    <docking:TabbedMdiContainer>
                        <ItemsControl ItemsSource="{Binding}" ItemTemplate="{StaticResource WorkspaceTemplate}" />
                    </docking:TabbedMdiContainer>
                </docking:TabbedMdiHost>
            </docking:Workspace>
        </docking:DockSite>
    </DataTemplate>
Many thanks!
Posted 9 years ago by Michael Per
Avatar
I see that other people had issues with MVVM as well and some came up with solutions. Actipro, do you have a reference sample for doing MVVM properly?
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Michael,

You won't be able to to put an ItemsControl in a TabbedMdiContainer. TabbedMdiContainers are created and destroyed dynamically as needed at runtime. Their content can only be DockingWindows so that the docking windows can also be moved in and out of the container, while the container is still in the UI.

We have an item on our TODO list to add a property on DockSite that would allow you to bind to some items source and generate documents based on it. It's one of our top 5 or so items on the TODO list, but before we get to another round of updates on Docking we have to get WPF Studio 2010.1 out and our first Silverlight product set out.

One thing you can do in the meantime is probably have an observable collection that you bind to your source. Then if an item is added to it, make a new DocumentWindow and add it to the DockSite.DocumentWindows collection and Activate() it. When the item is removed from your observable collection, find the related DocumentWindow and Destroy() it.


Actipro Software Support

Posted 9 years ago by Aled Hughes - Principal Software Engineer, Control Techniques Ltd.
Avatar
Hi Michael,
As Actipro support say, this type of pattern isn't directly supported by the MdiContainers but I hope it is added soon.
I explored some routes around this but it just added more complexity so for now I deal with it in code-behind, creating DocumentWindows on the fly as needed.
Aled.
Posted 9 years ago by Tim Coulter
Avatar
Is there any update on the expected release date for this enhancement?

It would be great to remove all my MDI codebehind and replace it with a binding :)
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Tim,

Now that Silverlight Studio is released, we've already jumped back on WPF development. We have a huge list of Docking/MDI enhancements that we are planning on working on starting this week. MVVM support is one of the items on our list to try and improve.


Actipro Software Support

Posted 9 years ago by Guus
Avatar
Hi there,

I'm having a hard time implementing this work around. Could you please elaborate on it some more? Maybe with a code sample?

This is my code as of the moment; MainWindow.xaml:

        <ContentControl
        Content="{Binding Path=Workspaces}"
        ContentTemplate="{StaticResource WorkspacesTemplate}"
        />
Where Workspaces is an ObservableCollections of WorkspaceViewModel objects which resides in my MainWindowViewModel class.

MainWindowResources.xaml:

    <DataTemplate DataType="{x:Type vm:CategoryViewModel}">
        <vw:CategoryView /> <!-- Is a UserControl -->
    </DataTemplate>

    <DataTemplate x:Key="WorkspacesTemplate">
        <TabControl 
          IsSynchronizedWithCurrentItem="True" 
          ItemsSource="{Binding}" 
          ItemTemplate="{StaticResource ClosableTabItemTemplate}"
          Margin="4" />
    </DataTemplate>
What do I need to do to get to WorkSpaces in my code-behind, so I would get something like this:

    <ContentControl
        Content="{Binding Path=DockingWindows}" <!-- Or how ever I get to my work around collection -->
        ContentTemplate="{StaticResource WorkspacesTemplate}" />
and this:

    <docking:TabbedMdiContainer 
        ItemsSource="{Binding}" 
        ItemTemplate="{StaticResource ClosableTabItemTemplate}"/>

    <DataTemplate x:Key="ClosableTabItemTemplate">
        <docking:StandardMdiHost
            Content="{Binding Path=DisplayName}" />
    </DataTemplate>
to work?

Kind regards,
Guus
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Guus,

You'd need to probably attach to the changed event of your Workspaces collection.

If you see that a WorkspaceViewModel is added, then create a new DocumentWindow (using a constructor that takes a DockSite) and pass it the appropriate child content. Then call DocumentWindow.Activate() to show it.

If you see that a WorkspaceViewModel is removed, you'd have to scan the DockSite.DocumentWindows collection for the related DocumentWindow and then call DocumentWindow.Destroy() on it to remove it from the UI and from being tracked by the DockSite.

Your core XAML hierarchy should just contain a DockSite with a Workspace in it, and with StandardMdiHost (or TabbedMdiHost) in the Workspace.

We are going to be working on enhancements for the 2010.2 version to better support data binding scenarios.


Actipro Software Support

Posted 9 years ago by Guus
Avatar
Hi,

I ended up doing this (for future reference):


        <docking:DockSite x:Name="dockSite"
                          themes:ThemeManager.Theme="{Binding ElementName=window, Path=(themes:ThemeManager.Theme)}">
            <docking:SplitContainer>
                <docking:Workspace>
                    <docking:TabbedMdiHost>
                    </docking:TabbedMdiHost>
                </docking:Workspace>

                <docking:ToolWindowContainer>
                    
                </docking:ToolWindowContainer>
            </docking:SplitContainer>
        </docking:DockSite>



        #region MainWindow Events

        protected override void OnActivated(System.EventArgs e)
        {
            var mainWindowVM = (MainWindowViewModel)window.DataContext;
            mainWindowVM.Workspaces.CollectionChanged += OnWorkspaceCollectionChanged;
        }

        #endregion

        #region Event handlers

        private void OnWorkspaceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null && e.NewItems.Count != 0)
                foreach (WorkspaceViewModel wsVM in e.NewItems)
                {
                    var dWindow = new DocumentWindow(dockSite);
                    dWindow.Title = wsVM.DisplayName;
                    dWindow.Unloaded += DocumentWindowUnloaded;
                    dWindow.Content = wsVM;
                    dWindow.Activate();
                }
        }

        private void DocumentWindowUnloaded(object sender, RoutedEventArgs routedEventArgs)
        {
            var dWindow = (DocumentWindow)sender;
            var wsVM = (WorkspaceViewModel)dWindow.Content;
            wsVM.CloseCommand.Execute("");
            dWindow.Destroy();
        }

        #endregion

Probably not the best solution, but it gets me there and I don't have time to figure out something fancy.

Thanks for your help.
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Guus,

That's roughly what you'd want to do. Although a couple more tweaks would be helpful:

1) In the XAML, you don't need the extra SplitContainer and ToolWindowContainer in there at all unless you are actually going to include a ToolWindow within the ToolWindowContainer's XAML.

2) Your OnWorkspaceCollectionChanged handler doesn't handle if your view model removes a WorkspaceViewModel, but maybe that never happens and is ok.


Actipro Software Support

Posted 9 years ago by Firefly
Avatar
It's a real shame this didn't make the cut for 2010.2
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hello all,

We are looking for beta testers for our new MVVM support. Please the our blog post for more information and email our support if you would like to sign up.


Actipro Software Support

The latest build of this product (v2019.1 build 0681) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.