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.
An option which works well for interaction between viewmodels is to bind directly to observer classes sitting between the viewmodel classes.
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With