views:

532

answers:

3

I'm been experimenting with the oft-mentioned MVVM pattern and I've been having a hard time defining clear boundaries in some cases. In my application, I have a dialog that allows me to create a Connection to a Controller. There is a ViewModel class for the dialog, which is simple enough. However, the dialog also hosts an additional control (chosen by a ContentTemplateSelector), which varies depending on the particular type of Controller that's being connected. This control has its own ViewModel.

The issue I'm encountering is that, when I close the dialog by pressing OK, I need to actually create the requested connection, which requires information captured in the inner Controller-specific ViewModel class. It's tempting to simply have all of the Controller-specific ViewModel classes implement a common interface that constructs the connection, but should the inner ViewModel really be in charge of this construction?

My general question is: are there are any generally-accepted design patterns for how ViewModels should interact with eachother, particularly when a 'parent' VM needs help from a 'child' VM in order to know what to do?


EDIT:

I did come up with a design that's a bit cleaner than I was originally thinking, but I'm still not sure if it's the 'right' way to do this. I have some back-end services that allow a ContentTemplateSelector to look at a Controller instance and pseudo-magically find a control to display for the connection builder. What was bugging me about this is that my top-level ViewModel would have to look at the DataContext for the generated control and cast it to an appropriate interface, which seems like a bad idea (why should the View's DataContext have anything to do with creating the connection?)

I wound up with something like this (simplifying):

public interface IController
{
    IControllerConnectionBuilder CreateConnectionBuilder();
}

public interface IControllerConnectionBuilder
{
    ControllerConnection BuildConnection();
}

I have my inner ViewModel class implement IControllerConnectionBuilder and the Controller returns the inner ViewModel. The top-level ViewModel then visualizes this IControllerConnectionBuilder (via the pseudo-magical mechanism). It still bothers me a little that it's my inner ViewModel performing the building, but at least now my top-level ViewModel doesn't have to know about the dirty details (it doesn't even know or care that the visualized control is using a ViewModel).

I welcome additional thoughts if there are ways to clean this up further. It's still not clear to me how much responsibility it's 'okay' for the ViewModel to have.

+1  A: 

I think you want to make your top-level ViewModel aware of the existence of the NestedViewModel, it makes sense from a hierarchical standpoint, the master view contains the child view.

In my opinion, your instinct is right, it doesn't feel correct for the nested ViewModel to expose behaviours which are initiated by user actions on the top-level. Instead, the top-level ViewModel should be providing behaviors for the view it is associated with.

But I'd consider moving responsibility for connection construction into an ICommand, and exposing this command via your top-level ViewModel. The OK button on your master dialog you would then bind to this command, and the command would just delegate to the top-level ViewModel, for example, call ViewModel.CreateConnection() when it is executed.

The responsibility of your nested control is then purely collecting and exposing the data to its NestedViewModel, for consumption by the containing ViewModel, and it is theoretically more re-usable in different contexts that require the same information to be entered (if any) - let's say you wanted to re-use it for editing already-created connections.

The only wrinkle would be if the different types of NestedViewModel expose a radically different set of data.

For example, one exposes HostName and Port as properties, and another exposes UserName and Password.

In which case you may need to do some infrastructural work to have your top-level ViewModel.CreateConnection() still work in a clean manner. Although if you have a small amount of nested control types, it may not be worth the effort, and a simple NestedViewModel type-check and cast may suffice.

Does this sound viable?

Leon Breedt
The challenge here is that the outer ViewModel doesn't actually know about the type of its InnerViewModel at compile time. Controllers are brought in as extensions to the application at runtime, with some type discovery under the hood to wire up the specific nested control via a custom DataTemplateSelector. I know that the DataContext will implicitly be the specific nested ViewModel, but it feels hackish to me to inspect the DataContext and try to cast it to a shared interface.
Dan Bryant
+3  A: 

An option which works well for interaction between viewmodels is to bind directly to observer classes sitting between the viewmodel classes.

Bermo
Thanks for the link; I was actually using a pattern similar to this for other coordination, by sharing an IMainViewModel service, which is implemented by my MainViewModel. I'm thinking it may make sense to refactor it further so that the 'shared' functionality is not tied up in the model for the main window and is, instead, a MainObserver.
Dan Bryant
This has been a helpful approach. My design is still a bit schizophrenic, but I'm starting to see how the VMs can communicate by using shared services. It feels a bit inverted, since I'm used to a 'parent' knowing everything about its 'children'. Now it's more a matter of my class saying "I need to get this done" and some part of the application that I know almost nothing about stepping up and taking care of it for me.
Dan Bryant
A: 

Hi,

I recently experimented with Unity (Microsoft Enterprise library) to use dependency injection. That might be a route to go when using interfaces that completely define what both viewmodels need to no from each other. MEF would be another option for dependency injection I'm aware of.

HTH

Sascha
Thanks, I am actually using MEF in this application and it has helped quite a bit in allowing very rich extensibility of the UI. It's this extensibility that creates design challenges, since now the UI is hosting controls that it really knows almost nothing about. I have actually come up with a cleaner way to do this, which I'll detail more once I get home today.
Dan Bryant