MVVM or: How I Learned to Stop Worrying and Love the ViewModel
By Johnson Luong, Software Engineer
Have you ever walked down the street and suddenly thought, “I just can’t figure out how to decouple my application model from its view to make it easier to refactor my code. What am I to do?” If so, do I have a solution for you! It’s a design pattern called MVVM, which is short for Model-View-ViewModel, and it separates development of the UI (the view) from your business logic and data (the model). This separation enables you to develop the UI and logic individually, and it ensures that a change to one won’t require a drastic change to the other.
The following diagram from Wikimedia illustrates this pattern. I’ll explain MVVM in depth below, but notice that with this pattern an application has three basic components:
These components are separate from one another, except for a few small connection points. The most important aspect of this pattern, however, is that the view is not aware of the model and vice versa. They communicate only through the viewmodel.
Fortunately, with Synergy .NET, implementing this design pattern is as easy as scoring bullseyes on womp rats in your T-16 back home. This is because there is a NuGet package that provides methods for this pattern for WPF apps. To show how it’s done and give you a sense of what MVVM can do, I’ll walk you through the creation of an MVVM app that displays user information pulled from Reddit’s API. I’ll start with a new Synergy .NET WPF app:
Next, using the Solution Explorer, I’ll create three folders (Models, Views, and ViewModels) for the three MVVM component types:
And I’ll add two NuGet packages to the project:
- Toolkit.Mvvm – This is the package that’s going to help me implement MVVM. This isn’t required to implement MVVM, but it does provide crucial functions for the MVVM pattern, functions that I’d rather not code myself. I think the phrase is, “Don’t reinvent the MVVM.”
- Text.Json – Since this application will read data from a JSON file, I’ll also add this package, which makes it easy to work with JSON.
Now I’m ready to begin work on my code. I’ll start with the model. In the Models folder, I’ve created a class called User in User.dbl. This class holds properties of the users in Reddit’s database. In this case, the application uses the model only to describe the shape of user data, nothing else. (Note: Each code sample below is also a link to GitHub, in case you want to copy and paste the code into your own project.)
Notice the use of the JsonPropertyName attribute, which is available because I added the System.Text.Json package. When the JSON shown below gets deserialized into User objects, this attribute will help the deserializer map the JSON properties to these class properties in the model. For example, the deserializer will map the snoovatar_img property in the JSON to SnoovatarImg, which adheres to .NET naming practices.
The following is the JSON data I’ve gathered for this application. I queried Reddit’s API for five users and truncated the results to the properties I’ll use in the application. See how it matches the User class above?
With the model finished and the backing data in place, I can move on to the viewmodel. The viewmodel’s purpose is to translate data from the model to the view. Without a viewmodel, the view won’t understand how to display data from the model or from any database store.
I’ll call my viewmodel DataGridViewModel and put it in DataGridViewModel.dbl in the ViewModels folder.
The most important part of this viewmodel is that it extends Microsoft.Toolkit.Mvvm’s ObservableObject. ObservableObject has a base implementation of INotifyPropertyChanged, which notifies the view that a property in the viewmodel has changed. If the viewmodel didn’t extend ObservableObject, the view would never know that data in the viewmodel had changed. It would look like a blank screen. Incidentally, I could implement INotifyPropertyChanged myself (this is what I was referring to when I said Microsoft.Toolkit.Mvvm isn’t required), but I chose not to because Microsoft.Toolkit.Mvvm includes a well-tested implementation of INotifyPropertyChanged, along with other useful features.
Now it’s time to add the view itself. I’ll create a new UserControl called DataGridView in the DataGridView.xaml file in the Views folder. The view will read the properties of the viewmodel and display it to the user. Note that I set the DataContext in the UserControl to the viewmodel. This sets up the bindings and completes the connection between the model and the view.
Astute readers may notice the EpochToDateTime converter, which I haven’t mentioned until now. This converts the user creation timestamp, which is in Unix time, to a more human-readable format.
To implement the converter, I’ll create a folder called Converters and add the EpochToDateTime class to a file (EpochToDateTime.dbl0 in that folder:
Finally, I’ll make a change to MainWindow1.xaml so that it uses DataGridView.xaml. MainWindow1 does nothing but house this view.
And that’s it!
Notice I didn’t touch the code-behind files. That’s the beauty of MVVM! If I ever need to change my UI, I won’t have to refactor anything else.
Here’s the application’s display of the Reddit data:
Feel free to build and play around with the view, viewmodel, and model classes used here to see how they interact with each other. The full solution is available at github.com/Synergex/MVVMProject. Check it out!
Synergy installers and MVVM
Fun fact: Did you know that our installers are also made with MVVM? Behind the scenes, the application controls which screens show, controls which buttons appear, and communicates with the installer to install the features you select. Without MVVM, each screen of the installer would need to be coded individually, leading to a lot of code duplication. Additionally, as these installers went through development iterations, MVVM made it possible to rapidly prototype changes to the UI and logic.
But wait, there’s more…
There’s more to Microsoft.Toolkit.Mvvm than what we discussed above, so here’s a brief rundown of a couple of other important aspects of this package:
- Messenger enables viewmodels to communicate with one another and share data. For example, the Synergy/DE installers use Messenger to communicate between views and show only information relevant to the product you’re trying to install.
- Support for dependency Injection enables you to better implement Inversion of Control (IoC). IoC allows users to control your applications, rather than the other way around (where the application controls how users interact with it).
So the next time you walk down the street and suddenly wonder how you are going to decouple your views from your models, just remember MVVM! It’s a palindrome, just like tacocat. With MVVM, you separate your view from your business logic, so it won’t be so bad if you decide to replace snoovatars with tacocats, racecars, or kayaks.