Mare Infinitus
Mare Infinitus

Reputation: 8172

Driving a MVVM application

Given any intermediate MVVM application which has more than 5 views and viewmodels, are there any recommend design patterns of how to do the scaffolding of such an application?

Right now I usually have a controller which is created in App.OnStartup which:

I believe there are already good design patterns, but non of them I have heard of or read about.

So the question is: Is there any commonly accepted pattern of how to handle the coupling of viewmodel and view (setting the datacontext) and the navigation between views?

In my opinion both view-first (setting the DataContext in XAML) and ViewModel-First (let the viewmodel get the view injected via DI/IOC) are not that good because they have make up dependencies between view and viewmodel.

Plain MVVM makes no assumptions on how to set up the whole MVVM machine. I'm just wondering that this quite common problem has no "of-the-shelf" solution. Controllers are widely used I believe. How do others solve that?

Upvotes: 0

Views: 327

Answers (3)

Steve
Steve

Reputation: 6424

I have a smallish project that contains a singleton class called ViewFinder that has a couple static methods called MakeWindowFor(vm) and MakeDialogFor(vm), that both take the viewmodel as a parameter. ViewFinder has a Dictionary that I fill that links viewmodels with the windows I've set to correspond to them. More information could be added, because perhaps the view lives inside another instead of simply being a window.

This may not be the best way to accomplish the task, but works for my needs on this project, and keeps the viewmodels unaware of the actual view implementation. The ancestor of all my viewmodels contains events for things like displaying message boxes, and all my windows are descended from a base class that know how to subscribe and react to these common events.

public class ViewFinder {
    private static ViewFinder m_Instance;
    public static ViewFinder Instance {
        get {
            if (m_Instance == null)
                m_Instance = new ViewFinder();
            return (m_Instance);
        }
    }

    /// Maps viewmodels to windows/dialogs. The key is the type of the viewmodel, the value is the type of the window.
    private Dictionary<Type, Type> ViewDictionary = new Dictionary<Type, Type>();

    /// Private constructor because this is a singleton class.
    ///
    /// Registers the viewmodels/views.
    private ViewFinder() {
        Register(typeof(SomeViewModel), typeof(SomeWindowsForViewModel));
        Register(typeof(SomeViewModel2), typeof(SomeWindowsForViewModel2));
    }

    /// Registers a window with a viewmodel for later lookup.
    /// <param name="viewModelType">The Type of the viewmodel. Must descend from ViewModelBase.</param>
    /// <param name="windowType">The Type of the view. Must descend from WindowBase.</param>
    public void Register(Type viewModelType, Type windowType) {
        if (viewModelType == null)
            throw new ArgumentNullException("viewModelType");
        if (windowType == null)
            throw new ArgumentNullException("windowType");
        if (!viewModelType.IsSubclassOf(typeof(ViewModelBase)))
            throw new ArgumentException("viewModelType must derive from ViewModelBase.");
        if (!windowType.IsSubclassOf(typeof(WindowBase)))
            throw new ArgumentException("windowType must derive from WindowBase.");
        ViewDictionary.Add(viewModelType, windowType);
    }

    /// Finds the window registered for the viewmodel and shows it in a non-modal way.
    public void MakeWindowFor(ViewModelBase viewModel) {
        Window win = CreateWindow(viewModel);
        win.Show();
    }

    /// Finds a window for a viewmodel and shows it with ShowDialog().
    public bool? MakeDialogFor(ViewModelBase viewModel) {
        Window win = CreateWindow(viewModel);
        return (win.ShowDialog());
    }

    /// Helper function that searches through the ViewDictionary and finds a window. The window is not shown here,
    /// because it might be a regular non-modal window or a dialog.
    private Window CreateWindow(ViewModelBase viewModel) {
        Type viewType = ViewDictionary[viewModel.GetType()] as Type;
        if (viewType == null)
            throw new Exception(String.Format("ViewFinder can't find a view for type '{0}'.", viewModel.GetType().Name));
        Window win = Activator.CreateInstance(viewType) as Window;
        if (win == null)
            throw new Exception(String.Format("Activator returned null while trying to create instance of '{0}'.", viewType.Name));
        win.DataContext = viewModel;
        return win;
    }
}

Upvotes: 1

EtherDragon
EtherDragon

Reputation: 2698

Some design patterns to consider are Inversion of Control (IoC) and Event Aggregator.

For C# / MVVM, the Caliburn Micro Framework (is one of a couple that) makes IoC and Event Aggregator much easier.

You correctly identified a core concern of MVVM in that there is no off-the-shelf solution to truely decouple the ViewModel from the View. It is a core concept that ViewModels are purpose built to be pared with Views. The issue comes down to how to manage instances of ViewModel / View pairings.

View first approach assumes that the View knows about and can instantiate ViewModels as needed - this is a problem for SoC because any View class now has multiple responsibilities; spinning up a ViewModel, and handling UI.

View Model first is difficult because it often leads to breaking one of the main tennants of MVVM - that the VM should be testable without any associated views.

This is where IoC comes in, typically. The IoC typically resides in the View layer (this is to allow it to have access to all View and ViewModel classes as needed) it need not be a View itself. It's often better to think of your IoC manager as a Controller - which kind of leads to a pseudo pattern of MVCVM. The sole purpose of this "controler" becomes providing View and ViewModel instance pairings to whoever needs it.

Event Aggregator pattern really helps with this because classes in the ViewModel and View no longer need to worry about who they are paired with, and can interract only with other classes in their own level. A particular View Model need not care who sent the event "Update Load Progress" all it needs to be responsible for processing the results of the event by setting it's progress property.

Upvotes: 1

Fede
Fede

Reputation: 44038

Regarding the "link" between the View and the ViewModel, I found the concept of the DataTemplateManager in this post really interesting. Basically, it allows you to do things like

DataTemplateManager.Register<TViewModel1,TView1>();
DataTemplateManager.Register<TViewModel2,TView2>();
DataTemplateManager.Register<TViewModel3,TView3>();

it might not be the best solution, admittedly, but is pretty handy. I already incorporated that into my own homemade MVVM framework.

Upvotes: 1

Related Questions