Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MvvmCross for WPF: A Basic Primer

0.00/5 (No votes)
7 Jul 2020 1  
An overview of MvvmCross for WPF application development
This article will provide a basic platform from which you can delve deeper into other aspects of MvvmCross.

Introduction

MvvmCross is a cross-platform MVVM framework that supports development of Xamarin, UWP and WPF applications. It features a view model first navigation system enabling navigation from one view to another based on a specified view model.

In this article, I will go over some of the features of the framework that are used for WPF application development, including a brief look at its navigation system. Kindly note that this article is not an introduction to WPF or MVVM so you should have knowledge of the two if you are to comfortably follow along.

Background

In my previous article, I covered some basics of WPF application development using the Prism library. This article covers MvvmCross using a sample application that's similar to the one in the Prism article. The application displays a view with some cards containing some employee details and another view that displays some more details of a specific employee. You can clone or download the sample project from GitHub.

Image 1

The sample application consists of two projects: a .NET Standard library project and a WPF app (.NET Framework) project.

Image 2

MvvmCross

Core

The 'Core' is a .NET Standard library that references the MvvmCross NuGet package and forms the heart of an MvvmCross application. It's where MvvmCross [mostly] expects you to define your models, services and view models, and where type registration with the MvvmCross IoC container is done.

The App class, which inherits MvxApplication, is where types are registered with the IoC container and the view model that should be navigated to first is specified.

using MvvmCross;
using MvvmCross.ViewModels;
using StaffStuff.Core.Services;
using StaffStuff.Core.ViewModels;

namespace StaffStuff.Core
{
    public class App : MvxApplication
    {
        public override void Initialize()
        {
            base.Initialize();

            Mvx.IoCProvider.RegisterSingleton<IStaffData>(new StaffData());

            RegisterAppStart<StaffViewModel>();
        }
    }
}

In the Initialize() method, I'm registering a service of type IStaffData and specifying that StaffViewModel, or rather its corresponding view, should be displayed first.

View models in an MvvmCross application must inherit the framework's MvxViewModel class. This class contains the framework's implementation of INotifyPropertyChanged and some view model lifecycle methods which you can override for specific purposes.

using MvvmCross.Commands;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
using StaffStuff.Core.Models;
using StaffStuff.Core.Services;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace StaffStuff.Core.ViewModels
{
    public class StaffViewModel : MvxViewModel
    {
        private List<Employee> _employees;
        public List<Employee> Employees
        {
            get => _employees;
            set => SetProperty(ref _employees, value);
        }

        private readonly IStaffData _staffData;
        private readonly IMvxNavigationService _navigationService;
        public IMvxAsyncCommand<Employee>
          EmployeeDetailsCommand => new MvxAsyncCommand<Employee>(StaffDetails);

        public StaffViewModel
             (IStaffData staffData, IMvxNavigationService navigationService)
        {
            _staffData = staffData;
            _navigationService = navigationService;
        }

        private async Task StaffDetails(Employee employee)
        {
            await _navigationService.Navigate<StaffDetailsViewModel, Employee>(employee);
        }

        public override async Task Initialize()
        {
            await base.Initialize();

            Employees = _staffData.GetEmployees();
        }
    }
}

The overridden Initialize() method is called after the view model has been navigated to and is where you should call any potentially blocking code that should be initially executed.

Navigation

Navigation in an MvvmCross app is done using the framework's navigation service. In the StaffDetails() method in StaffViewModel, the service is used to navigate and pass a parameter to StaffDetailsViewModel. The parameter is an Employee object.

using MvvmCross.Commands;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
using StaffStuff.Core.Models;
using System.Threading.Tasks;

namespace StaffStuff.Core.ViewModels
{
    public class StaffDetailsViewModel : MvxViewModel<Employee>
    {
        private readonly IMvxNavigationService _navigationService;

        public IMvxAsyncCommand GoBackCommand => new MvxAsyncCommand(GoBack);

        public StaffDetailsViewModel(IMvxNavigationService navigationService)
        {
            _navigationService = navigationService;
        }

        private Employee _employee;
        public Employee Employee
        {
            get => _employee;
            set => SetProperty(ref _employee, value);
        }

        public override void Prepare(Employee parameter)
        {
            Employee = parameter;
        }

        private async Task GoBack()
        {
            await _navigationService.Close(this);
        }
    }
}

The overridden Prepare(TParameter parameter) method is used when navigation with a parameter is expected. Note that you should only use this method to get parameters passed to a view model. Do not execute any other logic in this method. If you need to do so, use the previously mentioned Initialize() method.

In the GoBack() method, the navigation service's Close() method is called to close the view, triggering navigation back to the previous view.

Platform (WPF)

The expected approach when setting up an MvvmCross solution is to place platform specific code in projects associated with a target platform. In the sample solution, the StaffStuff.WPF project contains code that is specific to a WPF application. This project references the MvvmCross WPF platform NuGet package and the solution's core project.

The first step in setting up the WPF project to be MvvmCross compatible is by setting App.xaml.cs to inherit from MvxApplication and calling the required MvvmCross incantations.

using MvvmCross.Core;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Platforms.Wpf.Views;

namespace StaffStuff.WPF
{
    public partial class App : MvxApplication
    {
        protected override void RegisterSetup()
        {
            this.RegisterSetupType<MvxWpfSetup<Core.App>>();
        }
    }
}

You also need to make the necessary modifications to App.xaml:

<views:MvxApplication x:Class="StaffStuff.WPF.App"
                  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  xmlns:local="clr-namespace:StaffStuff.WPF"
                  xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;
                  assembly=MvvmCross.Platforms.Wpf"
                  StartupUri="MainWindow.xaml">
  <Application.Resources>

  </Application.Resources>
</views:MvxApplication>

Next, you have to make some modifications to MainWindow.cs by having it inherit MvxWindow:

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using MvvmCross.Platforms.Wpf.Views;

namespace StaffStuff.WPF
{
    public partial class MainWindow : MvxWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            ...
        }
    }
}

Corresponding changes have to be made to MainWindow.xaml:

<views:MvxWindow x:Class="StaffStuff.WPF.MainWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;
                          assembly=MvvmCross.Platforms.Wpf"
             xmlns:local="clr-namespace:StaffStuff.WPF"
             mc:Ignorable="d"
             Background="#FF0D2738"
             WindowStartupLocation="CenterScreen"
             Height="450" Width="800">
    <Grid>

    </Grid>
</views:MvxWindow>

The views, which are user controls, are also not spared these edits and have to inherit MvxWpfView:

using MvvmCross.Platforms.Wpf.Views;

namespace StaffStuff.WPF.Views
{
    public partial class StaffView : MvxWpfView
    {
        public StaffView()
        {
            InitializeComponent();
        }
    }
}

As you've probably figured out by now, an associated change has to be made to the XAML file.

With these processes done, the application is ready to be run. MvvmCross will associate the views with their corresponding view models and navigate accordingly when required to.

Conclusion

You should, if you didn't, now have some knowledge with which you can delve further into the MvvmCross framework. As I covered Prism in my previous article, it would be in order if I mentioned my comparisons of the two.

Prism seems to be much easier to work with: There are Visual Studio templates available which made setting up relevant projects an easy go. MvvmCross, on the other hand, doesn't seem to have any VS extensions that provide templates for WPF projects. Try as I might, I could not find them so had to resort to manually typing in all the various MvvmCross incantations

Also, I like using MahApps to customize WPF application windows. In the Prism sample app, I was able to comfortably use it but with MvvmCross, I just had to settle for the default system window. This was due to MainWindow having to inherit MvxWindow and in order to use MahApps, MainWindow has to inherit MetroWindow. Since multiple inheritance is not a C# feature MahApps was off the table. My attempts at finding a suitable solution were unsuccessful but maybe I will be enlightened in the comments.

History

  • 7th July 2020: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here