1

I'd like to understand the best practice for structuring an MVVM solution.

Currently I've created a separate project for my View and my ViewModel. My View project references my ViewModel project. So far so good. When it comes to implementing navigation my ViewModel classes need access to the RootFrame in order to navigate around, but the RootFrame resides in App.xaml which is in the View project. So I have a circular dependency issue here.

Is there a recommended structure I should be using? I could just lump this all into one big project but in order to decouple the View and ViewModel I feel like having separate projects is a better approach.

n00b
  • 5,843
  • 11
  • 52
  • 82

3 Answers3

3

When it comes to implementing navigation my ViewModel classes need access to the RootFrame

This is a false assumption.

You could use a message broker (a single object) that is responsible for distributing messages between publishers (ViewModels) and subscribers (some object that is responsible for opening Views).

Most MVVM frameworks have such a broker.

About the dependencies

The single responsibility of the Broker is to raise events. So, in general, it exposes a couple of methods that can be called by publishers and a couple of events that can be registered to by subscribers.

In MVVM you could use this mechanism to have a ViewModel raise the event that signals that a View should be opened and a View Manager that subscribes to this event. The View Manager should be able to instantiate a View and attach the correct ViewModel.

To prevent the ViewManager from needing references to all Views and ViewModels you could pass to the event logical names (just a string) and have the View Manager look up the matching View(Model) type by using reflection or a static list in a configuration file.

This way you do NOT need any circular references references. In fact, when you find you need references that go against the proper dependencies in MVVM, you should first doubt the setup and after that consider using a base class for the View and/or ViewModels.

Emond
  • 50,210
  • 11
  • 84
  • 115
  • Hi Erno, given my solution structure I'm not sure a message broker would help. Even if I packaged the broker in its own project, I believe I would have a circular dependency between the projects as the broker project would need references to both the View and ViewModel projects, and the ViewModel project would require a reference to the broker project. Could you verify or provide an example where a broker would help in the solution structure I currently have? – n00b Aug 25 '13 at 04:24
  • Thanks Erno. Do you have any samples you could link me? – n00b Aug 26 '13 at 05:47
  • Just look for tutorials on MVVM Light Messenger such as: http://dotnet.dzone.com/articles/mvvm-light-whats-messenger Googleing will also yield a lot of results – Emond Aug 26 '13 at 15:45
  • Thanks Erno. I used a messenger class to help with the navigation and keep the projects separate. Used this reference for those who are interested: http://jesseliberty.com/2011/01/06/windows-phone-from-scratch%E2%80%93mvvm-light-toolkit-soup-to-nuts-3/ – n00b Aug 27 '13 at 04:45
  • Yes, Jesse's blog is a nice resource. Be aware though that the code in this particular solution is hard to refactor when you rename a View (either name or namespace) – Emond Aug 27 '13 at 05:35
1

There is no best practice for MVVM since it is a design pattern that everybody implements differently according to their preference. I've seen quite a few different implementations but have never seen a views and view models in separate projects. I would recommend just keeping them in different folders in the same project and put them in different namespaces.

e.g. Your View Models can go in a ViewModels folder and be in the namespace MyProject.ViewModels

Your Views can go in a Views folder and be in the namespace MyProject.Views

And the same for Models if you are using them

KwanMan
  • 76
  • 2
  • 1
    I'll wait to see if there's a way to create separate projects for a view and viewmodel. If it's not possible then it seems that MVVM isn't really a modular architecture as a complete overhaul of the view would still require you to tinker within the project; whereas having the view in its own project would make such overhauls much easier. – n00b Aug 25 '13 at 04:32
0

Check out Rachel's good answer in this post. I also like to separate my Views and ViewModels, because then I know when I screwed up MVVM ground rules.

Your ViewModel should not have any references to the View, but the View must have a reference to the ViewModel. Consider, for example, my custom SplashScreen factory (the two important lines are "var viewModel..." and "var splashScreen..."):

namespace MyCompany.Factories
{
    using System.Threading;
    using MyCompany.Views;
    using MyCompany.ViewModels;

    public static class SplashScreenFactory
    {
        public static SplashScreenViewModel CreateSplashScreen(
            string header, string title, string initialLoadingMessage, int minimumMessageDuration)
        {
            var viewModel = new SplashScreenViewModel(initialLoadingMessage, minimumMessageDuration)
            {
                Header = header,
                Title = title
            };

            Thread thread = new Thread(() =>
            {
                var splashScreen = new SplashScreenView(viewModel);
                splashScreen.Topmost = true;
                splashScreen.Show();

                splashScreen.Closed += (x, y) => splashScreen.Dispatcher.InvokeShutdown();

                System.Windows.Threading.Dispatcher.Run();
            });

            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();

            return viewModel;
        }
    }
}

The Factories project has references to both MyCompany.ViewModels and MyCompany.Views. The Views project only has a reference to MyCompany.ViewModels.

We first initiate the ViewModel from our caller (in this case, from SplashScreenFactory; or maybe from App.xaml.cs if you want; I prefer using my own Bootstrapper class for reasons beyond this discussion).

Then we initiate the View by passing the ViewModel reference into the View's constructor. This is called Dependency Injection. You may also need to write a Behaviour for the Window class in order to Close the window from your ViewModel. So now I can do the following from my Bootstrapper class:

/// <summary>
/// Called from this project's App.xaml.cs file, or from the Deals Main Menu
/// </summary>
public class Bootstrapper
{
    private SplashScreenViewModel _splashScreenVM;

    public Bootstrapper()
    {
        // Display SplashScreen
        _splashScreenVM = SplashScreenFactory.CreateSplashScreen(
            "MyCompany Deals", "Planning Grid", "Creating Repositories...", 200);

        // Overwrite GlobalInfo parameters and prepare an AuditLog to catch and log errors
        ApplicationFactory.AuditedDisplay(
            Assembly.GetExecutingAssembly().GetName(),
            () =>
            {
                // Show overwritten version numbers from GlobalInfo on SplashScreen
                _splashScreenVM.VersionString = string.Format("v{0}.{1}.{2}",
                    GlobalInfo.Version_Major, GlobalInfo.Version_Minor, GlobalInfo.Version_Build);

                // Initiate ViewModel with new repositories
                var viewModel = new PlanningGridViewModel(new MyCompany.Repositories.PlanningGridHeadersRepository(),
                    new MyCompany.Repositories.PlanningGridLinesRepository(),
                    _splashScreenVM);

                // Initiate View with ViewModel as the DataContext
                var view = new PlanningGridView(viewModel);

                // Subscribe to View's Activated event
                view.Activated += new EventHandler(Window_Activated);

                // Display View
                view.ShowDialog();
            });
    }

    /// <summary>
    /// Closes SplashScreen when the Window's Activated event is raised.
    /// </summary>
    /// <param name="sender">The Window that has activated.</param>
    /// <param name="e">Arguments for the Activated event.</param>
    private void Window_Activated(object sender, EventArgs e)
    {
        _splashScreenVM.ClosingView = true;
    }

Note that I have control over the SplashScreen's View by subscribing to the View's Activated event, and then closing the view with the "ClosingView" boolean INotifyProperty that sets the View's "Close" DependencyProperty in turn (sounds complicated, but once you get to know DependencyProperties, it becomes simple).

The point is, don't try to drive your navigation from RootFrame. Just view.Show() the RootFrame, and control the rest from RootFrameViewModel.

Hope this helps :-)

Community
  • 1
  • 1
Riegardt Steyn
  • 5,431
  • 2
  • 34
  • 49