Creating Better Applications with MVVM

mileswatson

Miles Watson

Posted on January 9, 2021

Creating Better Applications with MVVM

In this article, I explain what MVVM is and how it can help you create better web/desktop applications. I use C#/WPF in my examples, although you should be able to follow along with basic knowledge of OOP and HTML.

Context

Until a month ago, my experience of creating graphical user interfaces was limited to plain HTML/CSS websites and some basic python applications using appJar. I always felt intimidated by the concept of having to work with GUIs: as a backend-oriented programmer, my way of thinking never seemed to mesh well with the popular frameworks.

Scared of frontend meme

Recently, I have had to started working on a desktop client for my decentralized social media. Most people would agree that this is an intimidating project, especially for an inexperienced frontend developer. Initially, I found making progress hard - until I learnt about MVVM.

What is MVVM?

MVVM stands for Model, View, View-Model. These form the three main parts of an MVVM application.

Alt Text

Models

Models contain core algorithms and data structures to be used by the application - the "business logic". These are often written in a "pure" programming language, without using any special libraries.

View-Models

View-models are special classes to represent the state of a view. For example, they have properties to represent displayed text, user-input textboxes, or even other view models. They should also have functions / commands which represent events (such as when a button is clicked).

View models often have some boilerplate code, so that they can integrate with whatever MVVM framework you are using. However, they should work on their own, with no dependency on the view.

Views

Views contain the code that is used by the UI. They are often written with a markup language (e.g. XAML or HTML). The views outline the structure (and styling) of the view, and their content is set with bindings.

Bindings allow content to be linked to a property of the view-model. Changing the property in the view-model will automatically update the shown content in the view. The binding can also work the other way, using a two-way binding. This is useful for textboxes, as you can have the view-model property update on each keystroke (this can be used for real-time input validation).

A Quick Example

To demonstrate the ideas I've talked about, I will quickly guide you through the process of creating an application using MVVM. The application will allow the user to click through a list of people.

I'll be using WPF (Windows Presentation Framework). If you want to follow along, create a new WPF App (.NET) project in Visual Studio. I've called mine Demo.

Creating a model

First, we'll create a class to represent a person. It will contain two fields - Name and Age.

I've put the class in the Demo.Models namespace, and saved it as Models/PersonModel.cs:



public class PersonModel
{
    public string Name { get; set; }
    public int Age { get; set; }

    public PersonModel(string name, int age)
    {
        Name = name;
        Age = age;
    }
}


Enter fullscreen mode Exit fullscreen mode

Creating the view model

Next, we will create the View Model. There will be four properties - two strings, and two commands. The strings will represent the name and age of the currently displayed person, whilst the buttons will allow us to click through the people.

Whenever we change a property, we have to alert the framework (so that the view can be updated). In WPF, this is achieved by implementing INotifyPropertyChanged and then calling NotifyPropertyChanged whenever a property is set.

I've put the class in the Demo.ViewModels namespace, and saved it as ViewModels/PeopleViewModel.cs:



public class PeopleViewModel : INotifyPropertyChanged
{
    //// Bindable properties

    private string _name;

    public string Name
    {
        get => _name;
        private set
        {
            _name = value;
            NotifyPropertyChanged(nameof(Name));
        }
    }

    private string _age;

    public string Age
    {
        get => _age;
        private set
        {
            _age = value;
            NotifyPropertyChanged(nameof(Age));
        }
    }

    public ICommand Previous { get; init; }

    public ICommand Next { get; init; }

    //// Implementation

    private PersonModel[] people = new PersonModel[] {
        new PersonModel("Alice", 20),
        new PersonModel("Bob", 25),
        new PersonModel("Charlie", 30)
    };

    private int index = 0;

    public PeopleViewModel()
    {
        Name = people[index].Name;
        Age = people[index].Age.ToString();

        Previous = new RelayCommand(() =>
        {
            if (index > 0)
            {
                index--;
                Name = people[index].Name;
                Age = people[index].Age.ToString();
            }
        });

        Next = new RelayCommand(() =>
        {
            if (index < people.Length - 1)
            {
                index++;
                Name = people[index].Name;
                Age = people[index].Age.ToString();
            }
        });
    }

    //// Boilerplate code to satisfy INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged is null) return;
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}


Enter fullscreen mode Exit fullscreen mode

In this example, the RelayCommand class is used to wrap functions as an ICommand. An explanation of this (and the code for the class) can be found here.

Why isn't the age an integer?

The age property isn't an integer, because it isn't representing the age of the person: it is representing the displayed content. The view will be displaying a string, therefore the property should be of type string.

Creating the view

Now that the view-model has been created, we will create a UserControl to act as the view. It will have two labels and two buttons - the labels will be bound to the Name and Age strings, and the buttons will bound to the Previous and Next commands.

In WPF, each view has a DataContext. By including the Demo.ViewModels namespace, we can set the data context of the user control to be a PersonViewModel. Then, we can bind to a property X of the UserControl.DataContext by using the "{Binding X}" syntax.

I've saved the user control as Views/PeopleView.xaml:



<UserControl x:Class="Demo.Views.PeopleView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:viewmodels="clr-namespace:Demo.ViewModels"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.DataContext>
        <viewmodels:PeopleViewModel />
    </UserControl.DataContext>

    <StackPanel Background="White">
        <Label Content="{Binding Name}" />
        <Label Content="{Binding Age}" />
        <StackPanel Orientation="Horizontal">
            <Button Content="Previous" Command="{Binding Previous}" />
            <Label />
            <Button Content="Next" Command="{Binding Next}" />
        </StackPanel>
    </StackPanel>
</UserControl>


Enter fullscreen mode Exit fullscreen mode

What about the code-behind?

The code-behind is rarely used in WPF applications that use MVVM. All the UI logic should be contained in the view-model, not the code-behind. This separation of concerns is what makes MVVM such a versatile architecture!

Finishing up

Now that we've created our view, we can display it in MainWindow.xaml.



<Window x:Class="Demo.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:Demo.Views"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <StackPanel>
        <views:PeopleView />
    </StackPanel>
</Window>


Enter fullscreen mode Exit fullscreen mode

Upon running, you should see the application working as expected:

demo1

Why all the effort?

You may be wondering why we should go through all this effort. There are three main reasons that MVVM is better than the standard "code-behind" approach.

Testability

Because your view-model is entirely decoupled from your view, you can use a testing framework (e.g. MSTest) to check that your interface logic is correct, without the need to manually test the UI yourself.

Modularity

Now that you have created your model and view-model pair, you can can reuse them as a component across your code. For example, you can add a second instance of the component like so:



<StackPanel>
    <views:PeopleView />
    <views:PeopleView />
</StackPanel>


Enter fullscreen mode Exit fullscreen mode

demo2

By nesting components within each other, you can build up complex, modular user interfaces with minimal effort.

You can even create multiple views bound to the same view-model! I've done this by setting the DataContext of the MainWindow to be a new PersonViewModel, and then binding both of the views to it.



<Window x:Class="Demo.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:viewmodels="clr-namespace:Demo.ViewModels"
        xmlns:views="clr-namespace:Demo.Views"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <viewmodels:PeopleViewModel />
    </Window.DataContext>

    <StackPanel>
        <views:PeopleView DataContext="{Binding}" />
        <views:PeopleView DataContext="{Binding}" />
    </StackPanel>
</Window>


Enter fullscreen mode Exit fullscreen mode

demo3

Note: You would normally bind the DataContext property of a view to a property of the current view-model. However, the main window doesn't have a view-model, and so I've bound it straight to Window.DataContext.

Platform Agnosticism

Splitting your application into the three parts allows you to reuse models and view-models across multiple platforms. This is made easy by using cross-platform frameworks like MvvmCross (which supports Windows, Mac, iOS and Android).

Conclusion

MVVM is an application architecture which allows you to develop testable, modular, and cross-platform applications. It splits code into three parts:

  • model
  • view-model
  • view

The separation of concerns improves code maintainability and readability. Furthermore, the model and view-model can be unit-tested and shared across platforms, allowing for quicker development of cross-platform applications.

Footnote

If you enjoyed reading this, then consider dropping a like or following me:

I'm just starting out, so the support is greatly appreciated!

Disclaimer - I'm a (mostly) self-taught programmer, and I use my blog to share things that I've learnt on my journey to becoming a better developer. I apologise in advance for any inaccuracies I might have made - criticism and corrections are welcome!

💖 💪 🙅 🚩
mileswatson
Miles Watson

Posted on January 9, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Creating Better Applications with MVVM