Last week, I covered the solution stack that I’m using for a new programming project. This week, I’ll go into more detail about one aspect of it: using the Model-View-ViewModel (MVVM) pattern in Universal Windows Platform (UWP) apps.
The Blank App Template
Visual Studio 2015 provides a template called Blank App (Universal Windows) that works as a starting point for UWP app development. Rather than creating a solution for my app project right away, I’m going to first experiment with a few key features of the solution stack.
The process of starting a new UWP solution using the Blank App template happens as follows:
- Open Visual Studio 2015
- Click New Project…
- Select templates — Visual C# — Windows — Universal — Blank App (Universal Windows)
VS then asks for some information about the new project and its solution. I’ll use the following values:
- Name: UWPMain
- Location: D:\Projects
- Solution name: UWP-MVVM-EF-SQLite
- Create directory for solution: Checked
- Create new Git repository: My preference is to leave this unchecked, and manually create a Git repository from the command line, just so I’m sure about what’s going on.
Here’s the result of those settings:
Create directory for solution is useful for organizing multiple projects under a solution. It defaults to checked, and that’s usually how you should leave it. The result is that VS creates a directory under Location with the same name as the solution, and puts the solution file inside that directory. In this case, that means it creates a directory called D:\Projects\UWP-MVVM-EF-SQLite containing a file called UWP-MVVM-EF-SQLite.sln.
By default, VS suggests giving the solution and the project the same name. But if you’re planning to add more than one project to a solution, it may be more clear to keep the names separate. That’s what I have done in this case: the solution is called UWP-MVVM-EF-SQLite (foreshadowing a discussion of other components in the solution stack). And the project is called UWPMain. As a result, VS creates a D:\Projects\UWP-MVVM-EF-SQLite\UWPMain directory and puts project-specific files there.
In the last step of the New Project process, VS asks about the minimum and target versions of Windows 10 for this UWP app. The defaults are fine.
At the end of the process, we have with the following files and directories under D:\Projects:
- UWP-MVVM-EF-SQLite: the solution directory
- UWP-MVVM-EF-SQLite.sln: solution-specific information, and references to projects in the solution.
- .vs: machine- and user-specific files. This directory is created automatically when the solution is loaded, so it doesn’t need to be tracked by Git.
- UWPMain: a project directory. All project-specific files for the UWPMain project go under here.
- UWPMain.csproj: project-level information, and references to files in the project.
- App.xaml: app-level settings for this UWP app.
- App.xaml.cs: C# code at the app level.
- UWPMain_TemporaryKey.pfx: UWP apps can be uploaded to the Windows Store, and part of that process involves signing the app with a digital certificate. The UWPMain_TemporaryKey.pfx certificate is self-signed, so it is only useful for running the app locally.
- MainPage.xaml: page-level markup for the app’s main page.
- MainPage.xaml.cs: C# code at the page level.
- Package.appxmanifest: According to MSDN, this file “contains the info the system needs to deploy, display, or update a Windows app.” Among other things, it includes information about the publisher (me), pointers to .png assets, and what types of devices the app targets.
- project.json: information about project references and dependencies.
- project.lock.json: the cached result of NuGet’s analysis of project dependencies. It gets generated automatically, so it shouldn’t be tracked by Git.
- Assets: 7 .png files of various sizes, used for the app logo.
- Properties\AssemblyInfo.cs: app properties, like description and version number.
- Properties\Default.rd.xml: runtime directives for .NET Native, a VS2015 precompilation technology used for UWP apps.
- bin and obj: directories containing the output of the compilation process.
When you run the blank app, you see a splash screen, followed by a single blank page.
Model, View, and ViewModel
In MVVM terminology, the Blank App only has a View. The template doesn’t include any Models or ViewModels. There’s nothing stopping developers from coding directly in MainPage.xaml.cs. That’s fine for simple apps, but as an app becomes more complex, there are advantages to using a more sophisticated pattern.
Let’s start with a very basic scenario: We have a Task class, and it has a Name property. That’s it. I’ll demonstrate basic XAML data binding using this single property and the MVVM pattern.
Model
The first step in writing this example app is to create the Model. In a full app, the model would contain business logic and database queries. But for this example, the Model is just one plain C# class with one string property. It is created as follows:
- In the UWPMain project: Add — New Folder — Models
- In the Models folder: Add — Class — Task.cs
Define the class as follows:
public class Task { public string Name { get; set; } }
(We can distinguish this Task class from the .NET Framework’s System.Threading.Tasks.Task
class the usual way, by using namespaces. I considered calling this class Activity
instead, but there’s a System.Activities.Activity
class as well. So I’m just going with the name that makes the most sense).
ViewModel
To isolate the model from user interface concerns, we’ll now create a View Model. By MVVM convention, if we have a class called Task
and we need to expose its data in the UI, we create a corresponding class called TaskViewModel
.
TaskViewModel
will be more complex than Task
, even in this simple example. That’s because the view model is responsible for setting up data binding by implementing the INotifyPropertyChanged interface.
To support data binding while keeping the view model code clean, I have taken the approach described by John Shewchuk in A Minimal MVVM UWP App: each view model class inherits from a base class, and the base class implements INotifyPropertyChanged. The base class in my example is forked from his NotificationBase class.
Here are the steps to create TaskViewModel
:
- In the UWPMain project: Add — New Folder — ViewModels
- In the ViewModels folder: Add — Class — NotificationBase.cs
- In the ViewModels folder: Add — Class — TaskViewModel.cs
Rather than show all of the code for these classes here, I have created a GitHub repository for it.
NotificationBase.cs contains a NotificationBase
class that inherits from INotifyPropertyChanged, and a NotificationBase<T>
class that inherits from NotificationBase
. TaskViewModel.cs is then fairly simple. It inherits from NotificationBase<Task>
and contains a private Task
reference, a public constructor, and a single Name
property. I’ll go into more detail below on how these three classes work.
View
The View code for this is example is located in MainPage.xaml and MainPage.xaml.cs. These files are created by the New Project wizard. They start out with some initial code, but we’ll add a few more things to demonstrate data binding.
Our data binding example has the following two components:
- A
TextBox
control that the user types in. - A
TextBlock
control that receives the user’s typed input through data binding.
I also added a few additional TextBlock
s that function as explanatory labels, and don’t participate in the binding process. That’s all. But despite the simplicity of the example, there’s quite a bit of detail to uncover as the binding magic happens.
To set up the view to demonstrate binding, add the following to MainPage.xaml.cs:
public TaskViewModel Task { get; set; }
Note that the View references the View Model (the TaskViewModel
class). It doesn’t directly reference the Model (the Task
class). The View doesn’t know that the Model exists.
In the MainPage
constructor, initialize the view model property:
Task = new TaskViewModel();
Next, add the following two elements to MainPage.xaml:
<TextBox Text="{x:Bind Task.Name, Mode=TwoWay}" />
<TextBlock Text="{x:Bind Task.Name, Mode=OneWay}" />
In a UWP app, a TextBox
accepts typed input in a box on the screen. Its Text
property can be used to read what the user types, or display text for the user to edit. We could write something like Text="some text"
to display literal text in the text box. Instead, we’re going to use the Text
property to set up data binding.
{x:Bind}
is a Windows 10 markup extension. It has some advantages compared to older XAML binding technology, including performance improvements (since binding happens at compile time).
For the text box, {x:Bind Task.Name, Mode=TwoWay}
means we want to bind the value in the text box to Task.Name
, the Name
property of the TaskViewModel
instance defined in MainPage.xaml.cs. And we want to bind it in TwoWay
mode, meaning that changes to the text box content updates the bound property, and changes to the bound property update the text box content. Two-way binding isn’t necessary for this example, but it will be useful as we expand this example in the future.
For the text block, {x:Bind Task.Name, Mode=OneWay}
means we want to bind the value shown in the text block to Task.Name
. Since text blocks are read-only, two-way binding is not required in this case.
Stepping Through the Data Binding Process
This example consists of four steps:
- Start the app.
- Type something in the text box.
- Exit the text box by pressing Tab or clicking elsewhere in the window.
- Observe the result in the text block.
There’s a lot going on under the covers during the data binding process, and understanding simple data binding is helpful when more complex scenarios need to be debugged. To see what’s happening at each step, I ran the example app in the debugger, and set a breakpoint at every location that looked interesting (i.e., at the top of every function or property). Here’s what I observed.
1. Start the app
- As a comment in the blank app template explains,
public App()
in App.xaml.cs “is the logical equivalent of main() or WinMain().” That’s where the app starts. It moves on toOnLaunched
in the same file. - The app’s main page code starts executing in the
MainPage
constructor. Since we put a line in there to initialize the task view model, that construction process starts, beginning with theNotificationBase<T>
constructor.
class NotificationBase<T>
contains a protected member called This
(note the capitalization) of type T
, which in this case is type Task
(the Task model). Since we didn’t pass a parameter to the constructor, This
is just initialized with a new Task
instance.
- Next, the
TaskViewModel
constructor executes, initializing its privateTask
instance. The View Model is allowed to refer to the Model, since its role is to mediate communication between the Model and the View. - Before MainPage displays for the first time, it needs to retrieve the initial value of the task name. That requires calling three getters.
- Getter #1: The
Task
getter inMainPage
returns aTaskViewModel
instance. - Getter #2: The
Name
getter inTaskViewModel
returnsThis.Name
. - Getter #3: Since
This
is an instance ofTask
, the third getter is theName
getter in theTask
class.
2. Type something in the text box
3. Exit the text box by pressing Tab or clicking elsewhere in the window
At this point in the execution, MainPage is visible, and we can start typing in the text box. No binding code executes while we’re typing (since the binding system isn’t fast enough to bind continuously), but once the text box loses focus, a few things happen:
- The
Task
getter inMainPage
returns theTaskViewModel
instance again. Since the view model’s Name field is bound to the text box, the
Name
setter inTaskViewModel
executes the following code to updateName
:set { SetProperty(This.Name, value, () => This.Name = value); }
Unlike a simple setter that just updates a local variable, this setter delegates the assignment logic to another method, the SetProperty
method in the NotificationBase
class. It passes these parameters:
This.Name
isTask.Name
, the current value of the Task model’s string property.value
is the value that the user typed in the text box. As with all setters, the .NET Framework provides it in this built-in variable.() => This.Name = value
is a lambda expression, a function that is passed as a parameter.()
means this function has no input parameters.=>
is the lambda operator, indicating that this is a lambda expression.This.Name = value
is the lambda body, the body of the function. This purpose of this function is to update the model’s Name property to the value that the user has typed.
The SetProperty<T>
method in NotificationBase
has four parameters, though only three need to be passed:
T currentValue
is the current value of the property to be set.T
isstring
in this case, since we’re updating a string property.T newValue
is the new property value that we want to set.Action doSet
is the function that will update the model property. Action is a .NET type that allows a function to be passed as a parameter (or assigned to a variable).[CallerMemberName] string property = null
, the fourth parameter, doesn’t have to be passed explicitly. It uses a handy feature that tells the compiler to look up the name of the field that we’re giving a new value to. This saves the developer from having to keep this value up to date, since we need the correct field name in this function.
SetProperty
has four steps:
if (EqualityComparer<T>.Default.Equals(currentValue, newValue)) return;
exits the function if the current and new values are equal. We don’t want to run unnecessary binding code if there’s nothing to update.doSet.Invoke()
executes the function that we passed as a parameter, which updates the Task model’s string property with the new value.RaisePropertyChanged(property)
raises an event, which is handled byRaisePropertyChanged
, a private method inNotificationBase
. The purpose of the event is to notify binding targets that the bound value has changed.RaisePropertyChanged
has a single line of code:PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property))
The ?.
is a null-conditional operator, which provides a concise way to only call PropertyChanged
if it is not null. The PropertyChanged
method itself is in a file called MainPage.g.cs that is generated by the compiler. One of the advantages of using {x:Bind}
is that you can step into this generated code with the debugger, to see exactly what’s going on. But for now, I won’t get into any more detail about code in MainPage.g.cs or the other generated source files. The important part is that it accomplishes the goal of sending a notification about the changed value.
4. Observe the result in the text block
The last step in the binding process is for the binding target to read the new value. The MainPage text block is bound to Task.Name
, the same property on TaskViewModel
that was updated in the previous step. A consequence of the PropertyChanged
event firing is that the text block asks for the updated value by calling the TaskViewModel
Name
getter, which gets the actual value from the Task
Name
getter in the model.
MVVM Fundamentals
There’s a lot more to MVVM and data binding, but this process of updating a single string value demonstrates the minimal file layout and execution process for the pattern to work. Next week, I’ll expand on the Model part of the pattern.
(Image credit: modified version of last week’s image)