The Model-View-ViewModel (MVVM) pattern helps developers separate an application's business and presentation logic from its user interface. Maintaining a clear separation between application logic and user interface helps address development and design issues, making an application easier to test, maintain, and develop. It can also improve the reusability of code and it allows multiple developers to collaborate more easily when working on the same project.
1. Introduction
Using the MVVM pattern, the user interface of the application and the underlying presentation and business logic are separated into three components:
- The view component encapsulates the user interface and user interface logic.
- The view model component encapsulates presentation logic and state.
- The model layer encapsulates the application's business logic and data.
There are several frameworks available for implementing the MVVM pattern in a Windows application. Which framework is best for your project depends on your requirements. For this tutorial, we will use MVVM Light, a popular and easy-to-use MVVM framework.
This tutorial shows you how to create a Universal Windows app with MVVM Light support. You will learn how to:
- create a Universal Windows app and add support for MVVM Light
- implement the directory structure
- add the view model layer
- wire the data context
- implement the messenger service to pass messages between view models
2. Project Setup
Step 1: Create a Universal Windows App
Let's start by creating a Universal Windows app. Select New Project from the File menu in Visual Studio. Expand Templates > Visual C# > Windows > Windows 8 > Universal and select Blank App (Universal Windows 8.1) from the list of project templates. Name your project and click OK to create the project.
This creates two new apps (Windows Phone 8.1 and Windows 8.1) and one shared project. The Windows Phone 8.1 and Windows 8.1 projects are platform specific projects and are responsible for creating the application packages (.appx) targeting the respective platforms. The shared project is a container for code that runs on both platforms.
Step 2: Add MVVM Light Support
Right-click the solution name in the Solution Explorer and select Manage Nuget Packages for Solution.
Select the Browse tab and search for MVVM Light. Select the package MvvmLightLibs from the search results. Check both the Windows 8.1 and Windows Phone 8.1 projects, and click Install to add the MVVM Light libraries to the apps.
At this point, you have added MVVM Light support to both your applications.
3. Project File Structure
A Universal Windows app that adopts the MVVM pattern requires a particular directory structure. The following snapshot shows a possible project file structure for a Universal Windows app.
Let me walk you through the project structure of a typical Univesal Windows app that adopt the MVVM pattern:
- Controls: This directory contains reusable user interface controls (application independent views). Platform specific controls are added directly to the platform specific project.
- Strings: This directory contains strings and resources for application localization. The Strings directory contains separate directories for every supported language. The en-US directory, for example, contains resources for the English (US) language.
-
Models: In the MVVM pattern, the model encapsulates the business logic and data. Generally, the model implements the facilities that make it easy to bind properties to the view layer. This means that it supports "property changed" and "collection changed" notifications through the
INotifyPropertyChanged
andINotifyCollectionChanged
interfaces. - ViewModels: The view model in the MVVM pattern encapsulates the presentation logic and data for the view. It has no direct reference to the view or any knowledge about the view's implementation or type.
-
Converters: This directory contains the value converters. A value converter is a convenient way to convert data from one type to another. It implements the
IValueConverter
interface. -
Themes: The Themes directory contains theme resources that are of type
ResourceDictionary
. Platform specific resources are added directly to the specific project and shared resources are added to the shared project. - Services: This section can include classes for web service calls, navigation service, etc.
-
Utils includes utility functions that can be used across the app. Examples include
AppCache
,FileUtils
,Constants
,NetworkAvailability
,GeoLocation
, etc. - Views: This directory contains the user interface layouts. Platform specific views are added directly to the platform specific project and common views are added to the shared project.
Depending on the type of view, the name should end with:
- Window, a non-modal window
- Dialog, a (modal) dialog window
- Page, a page view (mostly used in Windows Phone and Windows Store apps)
- View, a view that is used as subview in another view, page, window, or dialog
The name of a view model is composed of the corresponding view’s name and the word “Model”. The view models are stored in the same location in the ViewModels directory as their corresponding views in the Views directory.
4. Adding the View Model Layer
The view model layer implements properties and commands to which the view can bind data and notify the view of any state changes through change notification events. The properties and commands the view model provides, define the functionality offered by the user interface. The following list summarizes the characteristics, tasks, and responsibilities of the view model layer:
- It coordinates the view's interaction with any model class.
- The view model and the model classes generally have a one-to-many relationship.
- It can convert or manipulate model data so that it can be easily consumed by the view.
- It can define additional properties to specifically support the view.
- It defines the logical states the view can use to provide visual changes to the user interface.
- It defines the commands and actions the user can trigger.
In the next steps, we add two files to the view model layer, ViewModelLocator.cs and MainViewModel.cs.
Step 1: Add the MainViewModel
Class
First, right-click the shared project and select Add, New Folder. Name the folder ViewModels. Next, right-click the ViewModels folder and select Add, New Item to add the MainViewModel
class.
Modify the MainViewModel
class to look like this:
public class MainViewModel : ViewModelBase { private string _helloWorld; public string HelloWorld { get { return _helloWorld; } set { Set(() => HelloWorld, ref _helloWorld, value); } } public MainViewModel() { HelloWorld = IsInDesignMode ? "Runs in design mode" : "Runs in runtime mode"; } }
The class contains a public property HelloWorld
of type string
. You can add additional methods, observable properties, and commands to the view model.
Step 2: Add the ViewModelLocator
Class
We will add a public property for all the view models in the ViewModelLocator
class and create a new resource, which we will use in the designer.
Right-click the ViewModels folder and select Add, New Item. Select a class and name it ViewModelLocator.cs. Update the ViewModelLocator
class as shown below.
public class ViewModelLocator { public MainViewModel Main { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } } static ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); SimpleIoc.Default.Register<MainViewModel>(); } }
The ViewModelLocator
class contains a public property Main
whose getter returns an instance of the MainViewModel
class. The constructor of ViewModelLocator
registers the MainViewModel
instance to the SimpleIoc
service.
Next, open App.xaml file and add a new resource with the ViewModelLocator
to be used in the designer.
<Application.Resources> <viewModels:ViewModelLocator x:Key="Locator" /> </Application.Resources>
5. Wiring Up the Data Context
The view and the view model can be constructed and associated at runtime in multiple ways. The simplest approach is for the view to instantiate its corresponding view model in XAML. You can also specify in XAML that the view model is set as the view's data context.
<Page.DataContext> <vm:MainViewModel/> </Page.DataContext>
When the MainPage.xaml page is initialized, an instance of the MainViewModel
is automatically constructed and set as the view's data context. Note that the view model must have a default parameter-less constructor for this approach to work.
Another approach is to create the view model instance programmatically in the view's constructor and set it as the data context.
public MainPage() { InitializeComponent(); this.DataContext = new MainViewModel(); }
Another approach is to create a view model instance and associate it with its view using a view model locator. In the sample app, we use the ViewModelLocator
class to resolve the view model for MainPage.xaml.
<Page.DataContext> <Binding Path="Main" Source="{StaticResource Locator}" /> </Page.DataContext>
Now that the view's data context has been set to the MainViewModel
class, we can access its properties in the view. You can bind the text of a TextBlock
to the HelloWorld
property defined in the view model.
<TextBlock Text="{Binding HelloWorld}" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center"/>
6. Messenger Service
The messenger service in MVVM Light allows for communication between view models or between view models and views. Let's say you have a view model that is used to provide business logic to a search function and two view models on your page that want to process the search to show the output. The messenger would be the ideal way to do this in a loosely bound way.
The view model that gets the search data would simply send a "search" message that would be consumed by any view model that was currently registered to consume the message. The benefits of using a messenger service are:
- easy communication between view models without each view model having to know about each other
- more message consumers can be added with little effort
- it keeps the view models simple
To send a message:
MessengerInstance.Send(payload, token);
To receive a message:
MessengerInstance.Register<PayloadType>( this, token, payload => SomeAction(payload));
In the sample application, we will send a message from MainViewModel
, which will be received by MainPage.xaml. These are the steps required for using the messenger service.
Step 1: Create Class to Contain the Message to Be Passed
Create a new class in the project and name it ShowMessageDialog
.
public class ShowMessageDialog { public string Message { get; set; } }
Step 2: Instantiate Message Class and Broadcast Message
In MainViewModel.cs, create an instance of ShowMessageDialog
and use the Messenger
object to broadcast the message.
private object ShowMessage() { var msg = new ShowMessageDialog { Message = "Hello World" }; Messenger.Default.Send<ShowMessageDialog>(msg); return null; }
This broadcasts the message. All that is left for us to do, is to register a recipient and respond to the message.
Step 3: Register for Message and Handle When Received
Open MainPage.xaml.cs and register for the message in the constructor.
public MainPage() { this.InitializeComponent(); Messenger.Default.Register<ShowMessageDialog> ( this, (action) => ReceiveMessage(action) ); }
ReceiveMessage
is a method that you need to implement. It will take the Message
object and use the DialogService
to display a dialog box.
private async void ReceiveMessage(ShowMessageDialog action) { DialogService dialogService= new DialogService(); await dialogService.ShowMessage(action.Message, "Sample Universal App"); }
Step 4: Create a Command to Send Message
Now that we can send and receive a message, we need to call the ShowMessage
method. MVVM Light provides support for RelayCommand
, which can be used to create commands in the view model. Add a public property ShowMessageCommand
in the MainViewModel
class that invokes the ShowMessage
method.
private RelayCommand _showMessageCommand; public RelayCommand ShowMessageCommand => _showMessageCommand ?? (_showMessageCommand = new RelayCommand(ShowMessage));
Next, add a Button
to MainPage.xaml and bind the ShowMessageCommand
to its Command
property.
<Button Command="{Binding ShowMessageCommand}" Content="Click Me" HorizontalAlignment="Center"/>
Deploy the app to see if everything works as expected. Here's a snapshot of how MainPage.xaml looks on Windows 8.1.
When you click the Click Me button, a dialog box pops up.
Messenger is a powerful component that can make communication easier, but it also makes the code more difficult to debug because it is not always clear at first sight which objects are receiving a message.
Conclusion
By implementing the MVVM pattern, we have a clear separation between the view, view model, and model layers. Typically, we try to develop the view model so that it doesn’t know anything about the view that it drives. This has multiple advantages:
- The developer team can work independently from the user interface team.
- The view model can be tested easily, simply by calling some commands and methods, and asserting the value of properties.
- Changes can be made to the view without having to worry about the effect it will have on the view model and the model.
Feel free to download the tutorial's source files to use as a reference.
Comments