Need Support For V-M-VM Pattern

Docking/MDI for WPF Forum

Posted 15 years ago by Andy Ver Murlen
Version: 4.5.0485
Avatar
I am actually quite surprised to find that this doesn't work, but quite frankly considering WPF's major push for this pattern, it almost essential that it works. Now, for what doesn't seem to work....

I have a main window that has a dock site and I want to be able to add new tabs to it. Notice the "ItemsSource" binding. The DataContext of the window is set to a viewmodel that defines the "Workspaces" property....

<ribbon:RibbonWindow>
     <Grid>
          <docking:DockSite Grid.Column="2">
                <docking:Workspace>
                    <docking:TabbedMdiHost>
                        <docking:TabbedMdiContainer IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Workspaces}">                       
                    </docking:TabbedMdiHost>
                </docking:Workspace>
            </docking:DockSite>
          </ribbon:RibbonWindow>
     </Grid>
Now, we have a user control that is our "view" that we want to display in each tab, and it also has it's datacontext set to a viewmodel...

<UserControl x:Class="MyUserControl">
     <Some content here bound to my view model/>
</UserControl>

public class UserControlViewModel : INotifyPropertyChanged
{
    blah, blah, blah...properties the above user control can bind to
}
Now, we define a data template for the UserControlViewModel type that tells WPF how to display the UserControlViewModel type...

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="ViewModels"
    xmlns:UserControls="UserControls"
    xmlns:ribbon="http://schemas.actiprosoftware.com/winfx/xaml/ribbon"
    xmlns:themes="http://schemas.actiprosoftware.com/winfx/xaml/themes"
    xmlns:docking="http://schemas.actiprosoftware.com/winfx/xaml/docking"
    >
    <DataTemplate DataType={x:Type UserControls:UseMyrControl}>
        <docking:ToolWindow Title="{Binding Title}" ImageSource="{StaticResource Image}">
            <UserControls:MyUserControl />
        </docking:ToolWindow>
    </DataTemplate>
</ResourceDictionary>
As you can see, the data template tells WPF to render a UserControlViewModel as a docking:ToolWindow with it's content as the UserControl.

Then in the ViewModel we simply define the "Workspaces" property, and a method to add / remove from it...

public class MainWindowViewModel
{
    public ObservableCollection<UserControlViewModel> Workspaces {get; set;}

    public void AddWorkspace(UserControlViewModel workspace)
    {
         Workspaces.Add(workspace);
    }
}
Now, when we add a new workspace, WPF goes "oh, I know how to display that (as a toolwindow), and pops a new one into the TabbedMdiContainer's items.

This should work (it does with a tab control), and really needs to work in order for this great control to really fit with the WPF process. However it throws an error that "UserControlViewModel" cannot be added to a TabbedMdiContainer.

[Modified at 03/05/2009 08:16 AM]

Comments (17)

Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Andy,

Unfortunately there is no way you can set up a binding on a TabbedMdiContainer. The reason is that TabbedMdiContainers are dynamically added and removed at run-time based on windows being docked, opened, closed, etc. That concept simply wouldn't work.

However one thing we could possibly do in the future is add something to the DockSite itself like a DocumentsItemsSource property that would allow better support for binding and dynamic generation of document windows to natively fit the MVVM model.

As a side note, we've been working with a customer of ours, Mike Strobel, on adapters for having Docking/MDI (and Ribbon) work with the Prism (Composite WPF) framework. When using those adapters and classes, we do achieve the ability to support MVVM. If you are interested, we have a post about that here:
http://www.actiprosoftware.com/Support/Forums/ViewForumTopic.aspx?ForumTopicID=3274

Mike has the latest code bits, but we plan on making an "Actipro Contrib" open source CodePlex project soon with this in it.


Actipro Software Support

Posted 15 years ago by Andy Ver Murlen
Avatar
Thanks for the feedback, it makes sense once you explain the reason why. The thought on having a DocumentsItemsSource proptery the could be bound to a ICollectionView, which I would assume could simply call Activate() and Destroy() by handling the CollectionChangedEvent would be great.

If this was to happen, would the DocumentItemsSource property be able to handle adding a class (the view model) that has an appropriate datatemplaate defined as it's source?
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
We'd probably have to set it up almost like ItemsControl where you have protected virtual methods that by default would create a DocumentWindow container and then try and implement a way to pull down the implicit DataTemplate for the data type.


Actipro Software Support

Posted 15 years ago by Robert A. McCarter
Avatar
I was just in the process of converting my application to MVVM - so I'm very disappointed to hear that Actipro doesn't support this incredibly popular design pattern.

Right now I've got all the code wrapped up in the code behind files, and I would really like to be able to unit test the application's front-end. I was following along in Josh Smith's excellent MSDN MVVM article (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx) and I would really like to use Actipro instead of normal WPF tabs.

Any idea when you might build your proposed DocumentsItemsSource?

[Modified at 03/12/2009 09:22 PM]

[Modified at 03/12/2009 09:22 PM]
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Robert,

Thanks for your comments. The more feedback we get on this sort of thing, the higher it will get bumped on the priority list.

Would the potential solution mentioned above with DocumentsItemsSource accommodate what you need, or do you have additional requirements too?


Actipro Software Support

Posted 15 years ago by Robert A. McCarter
Avatar
Basically, the MVVM pattern (and all of us using it) have a collection of different types of POCO ViewModel classes (for example, one for clients, one for their accounts, one for support cases, etc). The goal is to display each in its own tabbed document. These classes expose ICommand instances for common operations which can then be bound to various command targets. So it would be really great to be able to bind a command to the tab's close "event" for example.

I also have (and this is from Josh’s article I linked to above) a POCO WorkSpace class, which maintains an observable collection of such ViewModel documents. Adding a new ViewModel object to this collection should automatically cause a new Actipro tab to appear to display the object. Removing an item from this collection should cause the document to be closed. If a document is closed then the item should be removed from the collection. The title and icon for the Actipro document should be easily data-bound to properties on the ViewModel class.

Standard WPF data binding should also determine the data template that will be used for different types of ViewModel objects. Thus, if I add a ClientViewModel instance to the collection, the client’s DataTemplate would contain a Tabbed Document whose content is the details of how I want to display the client. Obviously the document’s data context should automatically be the ClientViewModel instance.

For additional thoughts I'd suggest reading Josh's excellent article in MSDN magazine, which I linked to above.

[Modified at 09/03/2009 02:22 PM]
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Robert,

Thanks for the info, we'll keep this thread URL with the TODO item for reference.


Actipro Software Support

Posted 15 years ago by Jon von Gillern
Avatar
Consider my support for this functionality officially voiced. I just tried to do the same thing and found out I couldn't.

Thanks!
Posted 15 years ago by Andy Ver Murlen
Avatar
Slightly off topic, but I have found a small, but serious quirk with the tab control, and using Josh's "tabbed m-v-vm sample". My application is obviously much more complex than Josh's and it contains combo boxes that have the selected item bound to properties in the VM. However, when the contents of a tab control are set implicitly (i.e. setting the ItemsSource to our Workspaces property), it uses only one content presenter and resets it every time a new tab is selected. This causes 2 issues with the combo/list boxes in these views....

a. If you add a VM for a completely different view to your workspaces collection, when it's opened it will clear the selected items of the combo/list boxes in all currently open tabs.

b. When dealing with tabs having the same view as their content, selecting an item in a combo box in one tab selects the same item in the same combo box in all tabs.

I am wondering if you guys experienced this, and or figured a work around to it? My only work around thus far has been to display the data in a text box, and provide a "change" button that allows the user to select a new value.

For what it's worth, I have come up with a way (not perfectly clean) to still maintain my m-v-vm pattern using the tabbedmdi control, however it also exhibits this same undesirable behavior when switching tabs.
Posted 15 years ago by Eduardo Molteni
Avatar
I'm considering buying a Dock&Tab control, but M-V-VM support is a must.
Posted 15 years ago by Robert A. McCarter
Avatar
I've basically mimicked this by having a small class in the presentation tier watch an observable collection and automatically create appropriate tabs in the Actipro dock/tab control. It also listens for events from the Actipro dock/tab control and updates the underlying observable collection.

Not great, but it keeps my layers separated and it was a very small amount of code.
Posted 15 years ago by Boyd - Sr. Software Developer, Patterson Consulting, LLC
Avatar
I'll go ahead and throw in a few comments. I think I've gone the same route as Robert. I've been recently working with a MVVM implementation using Actipro controls, and originally ran into many of the issues already described here. With a little work, however, you can still build your applications using MVVM and Actipro.

We've already established that you cannot use a DataTemplate to "translate" a ViewModel into the corresponding view of a DockingWindow. DataTemplates are nice, but we got along for a long time without them. For my solution, I have a simple DocumentViewFactory object that is in charge of creating the corresponding View for each of my ViewModels. I essentially pass in the ViewModel to the factory class, and it creates the View for me. Basically the same thing as a DataTemplate. My "views" are typically a UserControl to be displayed as a MDI document. I then create a new DocumentWindow, set my view as the content, and then define my ViewModel as the DataContext. Activate the window and I'm good to go.

I also have a DocumentViewManager class that I have been working with to keep my ViewModel in sync with DockSite so that all that logic is kept in one class (i.e. closing a DocumentWindow removes the corresponding ViewModel from the underlying collection).

My point is that it can be done. It may not work as easily as some of Josh Smith's MVVM examples, but there is almost always a workaround.

I'm no expert on MVVM or WPF for that matter, so I may not fully comprehend everything others are trying to do. So far, so good.
Posted 15 years ago by Eduardo Molteni
Avatar
Robert: thanks for your comments. Maybe you have some code to share so we don't repeat ourselves (Read: So I don't need to write it :)
Posted 15 years ago by Robert A. McCarter
Avatar
Hmmm... I take it back - it's not a small amount of code! And when I went and re-examined my solution I didn't end up having an observable collection either. I've written so much code since then I forgot my final solution. Sorry about that.

I ended up creating interfaces in the view-model tier to represent a document manager and a document; both these interfaces are implemented in the presentation tier with classes that know all about Actipro. So it's not "true" MVVM but it keeps the separation very clean. It wouldn't be hard to make an observable view-model class implementation instead and then have a class in the presentation tier watch for property changes and collection changes, but I don't think it would be any more decoupled and it would certainly be messier code in the presentation tier.

Presention view-model tier:

The IDocumentManager interface has the following properties:
  • ActiveDocument
  • ClientSearchWindowVisibility - A read/write boolean property that indicates if the client search docked tool window is currently visible
  • HelpToolWindowVisibility - A read/write boolean property that indicates if the help docked tool window is currently visible
It has the following methods:
  • IDocument CreateClientDocument(ClientViewModel)
  • IDocument Create****Document(****ViewModel)
  • IDocument FindDocument(ViewModel)
It also has one event that is raised when the current document changes.

I have a static implementation of this interface. This static class has a property of type IDocumentManager called simply "Instance", which contains the concrete instance of the interface that all of the static member implementations delegate to (this is just for convenience). In the application start-up code this instance is set to the concrete instance to use (which comes from the WPF presentation tier). Once .NET 4.0 comes out I'll be switching this to use MEF (http://www.codeplex.com/MEF/) (unless Actipro improves this control to support MVVM out of the box! :-)

The IDocument interface contains the following self-explanatory properties:
  • Title
  • UndoServices
  • ViewModel
and various methods like:
  • Activate
  • Close
  • OnDocumentActivated
  • OnDocumentClosing
  • OnDocumentClosed
Presentation Tier
In the presentation tier I've got a base Document class which extends System.Windows.Controls.UserControl and implements the view-model tier's IDocument interface.

On the document manager when a document is activated for the first time I've got a method that creates a new Actipro window and ties it to the underlying presentation layer implementation of a document:

/// <summary>
/// Create the document's Actipro window.
/// </summary>
/// <param name="document">The <c>Document</c> object to use with the window.</param>
/// <returns>
/// The new document meta-data for this window.
/// </returns>
private Metadata CreateDocumentWindow( Document document ) {
    Check.ArgumentNotNullPrecondition(document, "The document to wrap in an Actipro window must not be null");

    // Create the Actipro document window
    string name = this.CreateUniqueWindowName();
    ActiproSoftware.Windows.Controls.Docking.DocumentWindow actiproWindow;
    actiproWindow = new DocumentWindow(this.DockSiteContainer, name, null, null, document);

    // Setup the data context for the window
    actiproWindow.DataContext = document.ViewModel;

    // Setup bindings for the document window's tab image to the documents's image
    Binding imageBinding = new Binding { Source=document, Path=new PropertyPath("ImageUri") };
    actiproWindow.SetBinding(DockingWindow.ImageSourceProperty, imageBinding);

    // Also bind the document window's title text to the documents's title
    Binding titleTextBinding = new Binding { Source=document, Path=new PropertyPath("Title") };
    actiproWindow.SetBinding(DockingWindow.TitleProperty, titleTextBinding);

    // Setup the document meta data and return it
    var metadata = new Metadata {
        Document=document,
        ActiproWindow=actiproWindow,
        ViewModel=document.ViewModel
    };
    m_Metadatum.Add(metadata);
    return metadata;
}
The method that calls this one ends up using the following code to actually activate the Actipro document:

// Activate the window
// This will trigger the documents Loaded event
metadata.ActiproWindow.Activate();
The various document class implementations usually bind the title (and sometimes the image too) to properties on the underlying view-model class.

The meta-data is my method of tracking which Actipro window is associated with which document instance (and view-model instance). The rest of my document manager implementation uses the metadata to implement the various IDocumentManager interface methods.

I know that's not what you were hoping for, but I hope you find it helpful.

Robert
Posted 15 years ago by Boyd - Sr. Software Developer, Patterson Consulting, LLC
Avatar
Funny how two people can come up with an almost identical solution. I pretty much did what Robert posted except I separated out my Create****Document methods into a factory object that uses the Type of the ViewModel to create the corresponding IDocument. That will hopefully make it easier for me to port my DocumentManager class to other applications by swapping in a new factory object.

Glad to see I wasn't the only one taking an approach like this.
Posted 15 years ago by Robert A. McCarter
Avatar
Me too, thanks for the feedback! :-)
My grandfather used to say "Great minds think alike, but fools seldom differ".
:-)
Posted 13 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 (v24.1.1) was released 2 months ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.