In my last post . I talked about navigation techniques in MVVM and how I didn’t like them too much. I also talked about learning Asp.NET Core is and how good the navigation / routing system is in there.

So, following the pain, I decided to create a lightweight navigation WPF framework similar to the one in Asp.NET Core. We’re still using MVVM, but adding controllers which makes it MVVMC, Model-View-ViewModel-Controller (This is how the library is called as well).

The code is available on GitHub .

Update 21/6/2018: I updated some features in the framework, added a NuGet package and added a thorough documentation on GitHub .

The following article is about using the navigation framework and how it implements the principles of MVVMC.

Getting started

To start with the MVVMC framework, simply install the NuGet Wpf.MVVMC to your WPF app.

Install-Package Wpf.MVVMC

Adding a Region

A Region is a Control with dynamic content. This is the part whose content you want changing when navigating. In my MainWindow:

<Window x:Class="MainApp.MainWindow"
        ...
        xmlns:mvvmc="clr-namespace:MVVMC;assembly=MVVMC">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Border >
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" >
                <Button>Home</Button>
                <Button>About</Button>
            </StackPanel>
        </Border>
        <mvvmc:Region ControllerID="MainOperation" Grid.Row="1"/>
    </Grid>
</Window>

The bottom of my window is the Region. This part is dynamic. The content of the Region can be changed and defined by a Controller (MainOperationController). A Controller controls only one Region and a Region is controlled by only one Controller.

The top part of the window, with two buttons is static and never changes.

File structure

A Controller and the Views & ViewModels connected to it should be in one namespace. You might want to have a separate folder for each Controller, like this:

Views

A view should be a FrameworkElement. So any User Control or a Custom Control will work. No need to derive from anything.

The only rule is that the name of the view should end with “View”.

A View and ViewModel pair is called a Page. So “AboutView” and “AboutViewModel” together make the page “About”.

ViewModels

A ViewModel should derive from MVVMCViewModel. Like this:

public class InitialViewModel : MVVMCViewModel
{
        void NavigateToAbout()
        {
            Controller controller = GetController();
            controller.Navigate("About", null);
        }
}

Another option is to derive from MVVMCViewModel<TController>. Like this:

public class InitialViewModel : MVVMCViewModel<MainOperationController>
{
    void NavigateToAbout()
    {
        MainOperationController controller = GetExactController();
        controller.About();
    }
}

Controllers

Here’s the MainOperationController example:

namespace MainApp.Pages.MainOperation
{
    public class MainOperationController : Controller
    {
        public override void Initial()
        {
            ExecuteNavigation();
        }

        public void About()
        {
            ExecuteNavigation();
        }

        public void AllEmployees(object parameter)
        {
            ExecuteNavigation();
        }

        public void AddEmployee(object parameter)
        {
            ExecuteNavigation();
        }
    }
}
  • A controller class should be called [ControllerID]Controller
  • A Controller should derive from MVVMC.Controller abstract class.
  • A Controller is connected to a single Region. When this Region is loaded, the controller is created and navigation to Initial is requested. Each controller should implement InitialI() method.
  • Each navigation request invokes a Method with the same name in the controller. So when navigation to “About” controller.Navigate(“About”, parameter: null);
    The “About()” method will be invoked.
    “About” is called the Action.
  • Optionally a navigation method can accept an object as a parameter.
  • When calling “ExecuteNavigation” from a method, the controller will create a View and ViewModel with the same name as the method. ```
    public void About()
      {
          ExecuteNavigation();
      }
      ```
    
      This code will:  
      1\) look for AboutView and AboutViewModel in the same namespace as the controller  
      2\) Create instances of them  
      3\) Set View.DataContext = ViewModel  
      4\) Change the Content of the relevant Region to be the View
    
    

Navigation options

From View:

You can use a special type of Command called NavigationCommand. Like this:

...
      <Button Margin="5" Command="{mvvmc:NavigateCommand ControllerID='MainOperation'}">Home</Button>
      <Button Margin="5" Command="{mvvmc:NavigateCommand ControllerID='MainOperation', Action='About'}">About</Button>
    </StackPanel>
  </Border>
<mvvmc:Region ControllerID="MainOperation" Grid.Row="1" Margin="5"/>
...

Pressing on About will find a controller called “MainApp.Pages.MainOperation.MainOperationController” and invoke the method About().

As you see, the Home Command doesn’t have an Action property set. This will bring us to Initial action.

From ViewModel:

Each ViewModel derives from MVVMCViewModel base class which exposes several methods for navigation:

  • GetController() will return the current control the ViewModel is related to. For example: ```
    public class InitialViewModel : MVVMCViewModel
      {
          void NavigateToAbout()
          {
              Controller controller = GetController();
              controller.Navigate("About", null);
          }
      }
      ```
    
  • NavigationService is a singleton and you can use it from each ViewModel: ```
    INavigationService svc = NavigationServiceProvider.GetNavigationServiceInstance();
      AddWizardController controller = svc.GetController();
      controller.Next();
      ```
    
      Using NavigationService this way is available from anywhere in the application.
    
    

All navigation methods allow to pass a parameter of type object. Here’s an example:
In ViewModel:

public class SelectEmployeeViewModel : MVVMCViewModel<AllEmployeesController>
{        
    public ICommand _selectEmployeeCommand;
    public ICommand SelectEmployeeCommand
    {
        get
        {
            if (_selectEmployeeCommand == null)
                _selectEmployeeCommand = new DelegateCommand(() =>
                {
                    GetExactController().Info(SelectedEmployee);//SelectedEmployee is the parameter
                },
                ()=>
                {
                    return SelectedEmployee != null;
                });
            return _selectEmployeeCommand;
        }
    }

In Controller:

public void Info(object parameter)
{
    var viewBag = new Dictionary<string, object>();
    var employee = parameter as Employee;
    viewBag.Add("EmployeeFirstName", employee.FirstName);
    ExecuteNavigation(parameter, viewBag);
}

ViewBag

A controller can populate the navigation target ViewModel with data in 2 ways: A parameter and a ViewBag. The ViewModel base MVVMCViewModel holds them as properties:

public abstract class MVVMCViewModel : BaseViewModel
{
    public Dictionary<string, object> ViewBag { get; set; }
    public object NavigationParameter { get; set; }
    public virtual void Initialize()
    {
        //This is called after NavigationParameter and ViewBag are set
    }

We can use the “NavigationParameter” and the “ViewBag” in Initialize() method. Or, there’s a special kind of binding available ViewBagBinding which automatically binds to the ViewBag. But it’s a simple “OneTime” binding (for now).
In View:

<StackPanel Orientation="Horizontal">
    <TextBlock Text="First Name: "/>
    <TextBlock Text="{mvvmc:ViewBagBinding Path=EmployeeFirstName}"/>
</StackPanel>

Regions inside Regions

We might have hundreds of screens in our App and we might want different controllers to control these screen. For example we can have a “main” controller and a controller for a “mini-flow”. This can be achieved by adding another Region in a View. In the sample application we have a “MainOperationController” and one of the views introduces another Region which is controlled by a different Controller:

AllEmployeesView in MainOperation:

<UserControl x:Class="MainApp.Pages.MainOperation.AllEmployeesView"
             xmlns:mvvmc="clr-namespace:MVVMC;assembly=MVVMC">
    <mvvmc:Region ControllerID="AllEmployees" />
</UserControl>

The View contains another Region. When loaded, AllEmployeesController will be called with Initial()

public class AllEmployeesController : Controller
{
    public override void Initial()
    {
        SelectEmployee();
    }

    public void SelectEmployee()
    {
        ExecuteNavigation();
    }

Since Initial calls SelectEmployee(), the Region’s content will become “SelectEmployeeView”

This finishes the technical overview. If you are curious, download the sample app from GitHub and run it. I’d love to hear your thoughts.

What more is missing?

There are still some important navigation features missing. Some are:

  • GoBack and GoForward navigation.
  • CanNavigate and CanNavigateBack functionality.
  • Better support for Testability – Have everything work with interfacs and dependency injection.

If there’s interest in this project I promise to add the missing functionality!

Is the MVVMC design pattern already being used?

I found out MVVMC (the design pattern, not my project) is already out there and some development teams are adopting it – Check out this blog post for example MVVM is dead, long live MVVMC! . I couldn’t find any WPF frameworks, so everyone who uses it probably wrote their own custom code. However, I did find a WinRT framework for MVVMC – ControllerRT.

ControllerRT is available on GitHub . I didn’t run it but I did look over the code. It looks good and somewhat similar to what I did. But also with many differences. For example there’s one controller for each View and ViewModel.

Summary

We saw how we can use a lightweight MVVMC framework for navigation in a WPF project. This provides a nice abstraction. Now, the View and ViewModel of a specific screen aren’t responsible for the navigation logic to a different screen.

I’ll use this in my future WPF projects, and hope some of you will try it as well. If you do try it, I’d love to hear feedback at michaels9876@gmail.com or by comments in this post.

I’d like to thank Aleksey Kravetz for sitting with me on the project and helping me with some of the concepts of this project!

M ↓   Markdown
?
Anonymous
0 points
7 years ago

Hi!

Great job! I'm wondering if there are other more common used frameworks based on MVVMC? Many MVVM stuff around related to WPF but not MVVMC!

So thank you very much for your contribution!

Greets Peter

?
Anonymous
0 points
7 years ago

Thanks Peter.
I looked for such when I wrote this and didn't find any MVVMC frameworks for WPF, just ControllerRT for WinRT.

?
Anonymous
0 points
7 years ago

"Unfortunately, besides some great feedbacks, I don't see developers adopting this framework."

Well, there are some (popular) MVVM frameworks with ViewModel-first navigation, like ReactiveUI.

?
Anonymous
0 points
7 years ago

Sure, ReactiveUI looks interesting.
I didn't mean all WPF navigation frameworks aren't be popular, just that this specific project isn't getting much traffic on GitHub.
Hope this changes with the better documentation and my new additions.

?
Anonymous
0 points
7 years ago

Question: is it possible to show modal views?

?
Anonymous
0 points
7 years ago

By showing modal views I suppose you mean showing an "overlaying" view, while the previous view remains "in the back". So like a dialog.
The regular navigation will always Unload the previous View and Load the new view, and there's no API to show modal view.

However, you can rather easily implement modal views with 2 controllers. For example, in the outer view (or Window) write:

&lt;grid&gt; &lt;mvvmc:region controllerid="Regular"/&gt; &lt;mvvmc:region controllerid="Modal"/&gt; &lt;/grid&gt;

The "Modal" controller will show an empty view by default, so only the "Regular" region will be seen. When you want to display a modal view, navigate with the "Modal" view, effectively covering the Regular view and acting like a modal view.

?
Anonymous
0 points
8 years ago

Awesome article, been viewing different options for this problem, and MVVMC seems to be a strong choice.

?
Anonymous
0 points
7 years ago

Fantastic job!
I like the freedom of not being tied to a "head controller" to allow for completely free navigation, yet on the other hand giving the option of having a "main controller" if so desired. I especially like how with MVVMC, there's even more component decoupling and SoC. One could work on navigation while another worked on ViewModel - View interaction and so on.

I would very much like to see continued work on this project, particularly the "missing features" you mentioned.

Keep up the good work!

?
Anonymous
0 points
7 years ago

Thanks Garrett, much appreciated!

Unfortunately, besides some great feedbacks, I don't see developers adopting this framework.
I'll definitely add features the next time I'll start a new WPF app and need them myself.
Same if someone starts using the project and has some issues.

For now though, it seems pointless to add more functionality if no-one is using it.

Although it's possible there's no adoption due to lack of activity, it's also very likely that even if I put in lots of effort, it won't help anyone.

Michael

?
Anonymous
0 points
7 years ago

I decided to give MVVMC a fighting chance after all :) I created a NuGet package + Some very thorough documentation on GitHub.

?
Anonymous
0 points
6 years ago

Hi,
I think, that the C in MVVMC is better interpreted as 'Coordinator', which tells Little more about the Job it does.

?
Anonymous
0 points
6 years ago

Yes, this all Controller madness started with MVC, they are the ones to blame