ViewModel first with Prism Integration (MVVM)

Docking/MDI for WPF Forum

Posted 6 years ago by Alan McCormick
Version: 12.2.0573
Avatar

I'm working with the Actipro Docking library and open source Prism integration lib. I was wondering if someone could help direct me on the best way to accomplish what I'm trying to do.

  • I want to use ViewModel first pattern in my code. I would like to create a ViewModel add that to a IRegion in Prism and have a DataTemplate in my resources create the View
  • I do not want to implement Actipro prism integration interfaces in my ViewModel (IDockingWindowInitializer, IDockingWindowItemTypeProvider). I want to be able to swap UIs and not have my ViewModels rely on Actipro's library.
  • I would like to have my application's logic continue to use Prism's regions (IRegion interface). Again, just to keep maximum flexibility

Here's the problems I'm facing...

  1. If I create a ViewModel and add it to a DockSiteRegion through Prism, nothing is displayed because my ViewModel doesn't implement IDockingWindowInitializer, IDockingWindowItemTypeProvider, which I explicitly do not want to do. I do not want to use these interfaces, because I likely have to wire to many UI events and implement UI specific commands (Float, Dock) for my base DockingWindow class. This is code that should not be in the ViewModel
  2. Another alternative, I tried was to create a "DockingWindowHost" class which implemented those interfaces. It had a ContentControl with Content="{Binding}". This allowed resolution of the DataTemplate for each ViewModel normally with WPF. But, to use this class I had to create my own service, and do something like "IDockingService.AddDocument(myViewModel)". The service would then resolve the prism region and create the DockingWindowHost, set the DataContext, and add it to the region. However, this still makes all my ViewModels dependent on some docking service, and I wanted to keep it relying on a normal IRegion within Prism.
  3. I tried completely ignoring IDockingWindowInitializer, IDockingWindowItemTypeProvider... and using my DataTemplate to wrap MyView inside a DocumentWindow, but I'm not sure what I would need to do to make it display the Window in the docksite. I added my ViewModel to my region, that should resolve the Template which has a root control of DocumentWindow... but then that never actually "opens". I'm not sure where I could open that either
  4. Whatever solution I have, I still need to be able to handle DragDrop onto TabPage Tabs and also be able to bind the DocumentWindow.Title to a property in my ViewModel

I'm looking for anyone that I can have a discussion with on the best implementation that meets my goals.. 

[Modified 6 years ago]

Comments (7)

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Alan,

I saw that you had posted in another thread that you never noticed the Prism Integration Sample.  Once you take a look at that in further detail, please write back whether you still have questions or if it answers things for you.


Actipro Software Support

Posted 6 years ago by Alan McCormick
Avatar

Yes, please re-read my question. I looked at the sample but I'm not happy with the way they did it. They are handling user interface code inside of their ViewModel with their ImplicitTemplates. Notice the following ViewModel which is not supposed to contain any UI references, directly handles docking and UI events. I'm trying to avoid this in my original quesiton.

 

/// <summary>
/// Represents a tool view-model for the sample.
/// </summary>
/// <remarks>
/// This view-model implements IDockingWindowInitializer and initializes the DocumentWindow directly.
/// </remarks>
public class ClassViewToolItemViewModel : IDockingWindowInitializer, IDockingWindowItemTypeProvider {

/////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERFACE IMPLEMENTATION
/////////////////////////////////////////////////////////////////////////////////////////////////////

#region IDockingWindowInitializer Members

/// <summary>
/// Initializes the specified docking window with any associated metadata.
/// </summary>
/// <param name="dockingWindow">The docking window that should be initialized.</param>
/// <param name="item">The associated item.</param>
public void Initialize(DockingWindow dockingWindow, object item) {
if (dockingWindow == null)
throw new ArgumentNullException("dockingWindow");

dockingWindow.ImageSource = new BitmapImage(new Uri("/Resources/Images/ClassView16.png", UriKind.Relative));
dockingWindow.Name = "classViewToolWindow";
dockingWindow.Title = "Class View";
}

/// <summary>
/// Opens the specified docking window with any associated metadata.
/// </summary>
/// <param name="dockingWindow">The docking window that should be opened.</param>
/// <param name="item">The associated item.</param>
public void Open(DockingWindow dockingWindow, object item) {
if (dockingWindow == null)
throw new ArgumentNullException("dockingWindow");

// Dock with the Solution Explorer, otherwise to the right
DockSite dockSite = dockingWindow.DockSite;
ToolWindow toolWindow = dockingWindow as ToolWindow;
if (dockSite != null && toolWindow != null) {
ToolWindow relativeToolWindow = dockSite.ToolWindows["solutionExplorerToolWindow"];
if (relativeToolWindow != null)
toolWindow.Dock(relativeToolWindow, Direction.Content);
else
toolWindow.Dock(dockSite, Direction.Right);
}
}

#endregion // IDockingWindowInitializer Members

Posted 6 years ago by Alan McCormick
Avatar

Basically I want a DataTemplate for my ViewModel which creates my View and I want the View to decide whether or not it is a ToolWindow / DocumentWindow and to handle Drag/Drop Float/Dock or whatever else the UI needs to know about.

Imaging you design an application with horizontal layers, business data, business logic, user interface. Refer to this link from the Prism book http://msdn.microsoft.com/en-us/library/gg405479(v=pandp.40).aspx#sec12

Now the modules / layer concerned with business logic (i.e. the ViewModel classes) has no idea about the user interface. How is it possible to use your prism integration to achieve this? You cannot have your ViewModel implement IDockingWindowTypeProvider or IDockingWindowInitializer. For example, say you want to swap a Tabbed MDI Host with a simple TabControl instead. You should be able to do this without modifying the anything but the UI Layer / Module. That's what I'm trying to achieve.

Normally, in MVVM it would be possible to use implicit data templates to allow for the creation of the View automatically when the ViewModel got added to a control. However, the way your integration lib works, I can't seem to think up any solution that would make this work.

I understand... this may sound picky and self-centered on what I'm trying to do, but I think what I'm doing is adhering to the Prism design methods including standard practices of SoC and separating business and UI logic. I have real concerns that I may want to swap out the docking library for a simpler interface on a tablet for example which doesn't have the screen real-estate a desktop computer would have. 

[Modified 6 years ago]

Posted 6 years ago by Alan McCormick
Avatar

Are you thinking this over? I'm still grappling with it myself. I looked into all sorts of different ideas. What I've seen in the MVVM Sample in the Controls Browser is much more in line with what I want to do, but I want it to work with Prism. Basically, I think I want two regions for my DockSite "Tools" and "Documents" (to represent the respectively bindable DocumentItemsSource and ToolItemsSource).

Document Windows
The DocumentItemsSource can be bound to a list of items, which will each be wrapped in a DocumentWindow. The DocumentItemContainerStyle, DocumentItemTemplate, and associated selector properties can be used to custom the style or template the associated window.

Tool Windows
The ToolItemsSource can be bound to a list of items, which will each be wrapped in a ToolWindow. The ToolItemContainerStyle, ToolItemTemplate, and associated selector properties can be used to custom the style or template the associated window.

 

 So, I want to be able to do the following:

TestViewModel viewModel = container.Resolve<TestViewModel>();
regions.Regions["Tools"].Add(viewModel);

Or,

TestViewModel viewModel = container.Resolve<TestViewModel>();
regions.Regions["Documents"].Add(viewModel);

The above would represent code that is agnostic of the specific UI controls. For example, in my application, I might respond to file open events, load the file's content and handle errors, assign the data to a ViewModel and then add them to a Region. 

Then, a DataTemplate could be used to create the UI:

<DataTemplate DataType="{x:Type loc:TestViewModel}">
	<loc:TestView />
</DataTemplate>

I think I could use the DocumentItemContainerStyle to customize my document windows if necessary... I believe that if my non-UI code is really to be independent of the docking library then there must be two regions for what I would consider "Tools" and "Documents". Otherwise, there would be no way to distinguish between the two different concepts. The current Prism integration library doesn't do this, and it is not possible to bind to the ItemsSource of a ToolWindowContainer or TabbedMdiContainer because those are created and destroyed at runtime. I must be able to bind to the DocumentItems and ToolItems of the DockSite itself with two separately named Prism IRegion interfaces.

How can I allow myself to interact with the DocumentItemsSource and ToolItemsSource as two separately named Prism IRegions?

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Alan,

I understand now, but there is a bit of a chicken and the egg problem here. The DocumentWindow/ToolWindow is needed to construct/present the view, so we can't really use the view to decide how to build the DocumentWindow/ToolWindow. To make matters even harder, the view can be defined implicitly in several different ways. So we can't just figure out what the view is and construct a temp version to discover how to initialize the DocumentWindow/ToolWindow.

The best solution would be to build a custom version of our DockSiteRegionAdaptor. The code used to decide what ViewModels would be a DocumentWindow and which would become a ToolWindow is located in the Adapt method. You can see were we set both the DocumentItemsSource and ToolItemsSource properties at the bottom of that method. The two lamba expressions in those lines are filters (i.e. ToolItemsSource returns true for anything that's not supposed to be a ToolWindow).

Our logic looks for the the IDocumentWindowItemTypeProvider, but you can look for specific ViewModel types or a property on your view models to decide which type to create.

Similarly, you would have to update the GetDocumentWindowInitializer in the DockSiteRegionAdapter to customize where the IDockingWindowInitializer comes from.


Actipro Software Support

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

As a followup to your latest reply (I think we both wrote at the same time), I don't think Prism plays nicely if you try to have two Regions on the same control. The region adapter is the place to bifurcate your items, as we describe above.


Actipro Software Support

Posted 6 years ago by Alan McCormick
Avatar

I'm not sure that there would be any problem with having two different IRegions which represent one control. Obviously, you would not be specify the name of the regions using the attached property, but you may be able to set it up to work in the code-behind. Another thought, was that perhaps you could setup two mock sub-regions Documents and Tools.

I wish I knew how to get it working that way, because I believe it would be more of a correct Prism / MVVM approach. Your MVVM example binds to DocumentItemsSource and ToolItemsSource. While Prism does that indirectly through the Interop library, my main problem with it is that when you add it to the region it all looks the same, when really they are different concepts and in the MVVM example they are treated that way. I definitely have a clear distinction in my application between what is data and what are tools relating to the data.

I think you have to look at the Prism integration library and really ask if it is reasonable for the ViewModels to implement IDockingWindowTypeProvider and IDockingWindowInitializer. I try to be flexible when possible, but this seems like clear problem. Currently, I'm implementing those interfaces in my UserControl. Or actually, a base class from which all my views inherit. In that base class (DockingViewBase) I actually have to wire up to quite a few DockingWindow events and handle them. This is a lot of UI code that I wouldn't want to litter my ViewModel outside of the UI. I think that is understandable.

Right now, I'm using View-First creation with property-injection of the ViewModel into the View. However, there are a few reasons I don't like it. With MVVM, typically your ViewModels are your application, you'd want to create a ViewModel and work with it directly. It feels more verbose and odd to have to use your Views to create your ViewModels. I also prefer constructor injection at all times, but that isn't possible with UI controls due to design time tools.

Here's an example

foreach (string file in imageFiles)
{
	try
	{
		ImageView view = container.Resolve<ImageView>();
		BitmapImage image = new BitmapImage(new Uri(file));
		view.ImageViewModel.Image = image; // awkward
		mainRegion.Add(view);
	}
	catch
	{
		args.AddError(new FileNoticationMessage(file, "The image file is invalid"));
	}
}

 Compared to,

foreach (string file in imageFiles)
{
	try
	{
		ImageViewModel viewModel = container.Resolve<ImageViewModel>();
		BitmapImage image = new BitmapImage(new Uri(file));
		viewModel.Image = image;
		mainRegion.Add(viewModel);
	}
	catch
	{
		args.AddError(new FileNoticationMessage(file, "The image file is invalid"));
	}
}

However, to give you credit the method I'm using still makes use of the Integration library and doesn't require the ViewModel to implement any UI interface. Another alternative for me to avoid making the View responsible for creating the ViewModel is to follow the "Marriage pattern" where they come together externally after separate creation -- neither is necessarily first. (http://wildermuth.com/2009/5/22/Which_came_first_the_View_or_the_Model)

I think I'll be able to use one of the above patterns and still meet all my goals. If I want to swap out UIs I can resolve an interface to my view instead of the view directly and swap implementations. It would be too much work for me to look into re-implementing a different version of Prism integration and I'm not sure exactly how that would work. It might be argued that I would have more control resolving an interface to the UI, albeit a bt more verbose.

The latest build of this product (v2018.1 build 0675) 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.