Red-Green-Code

Deliberate practice techniques for software developers

  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter

MVVM Fundamentals For UWP Apps

By Duncan Smith Jan 18 0

TT Technology 2

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 TextBlocks 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:

  1. Start the app.
  2. Type something in the text box.
  3. Exit the text box by pressing Tab or clicking elsewhere in the window.
  4. 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 to OnLaunched 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 the NotificationBase<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 private Task 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 in MainPage returns a TaskViewModel instance.
  • Getter #2: The Name getter in TaskViewModel returns This.Name.
  • Getter #3: Since This is an instance of Task, the third getter is the Name getter in the Task 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 in MainPage returns the TaskViewModel instance again.
  • Since the view model’s Name field is bound to the text box, the Name setter in TaskViewModel executes the following code to update Name:

    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 is Task.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 is string 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 by RaisePropertyChanged , a private method in NotificationBase. 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)

Categories: TT

Prev
Next

Stay in the Know

I'm trying out the latest learning techniques on software development concepts, and writing about what works best. Sound interesting? Subscribe to my free newsletter to keep up to date. Learn More
Unsubscribing is easy, and I'll keep your email address private.

Getting Started

Are you new here? Check out my review posts for a tour of the archives:

  • 2023 in Review: 50 LeetCode Tips
  • 2022 in Review: Content Bots
  • 2021 in Review: Thoughts on Solving Programming Puzzles
  • Lessons from the 2020 LeetCode Monthly Challenges
  • 2019 in Review
  • Competitive Programming Frequently Asked Questions: 2018 In Review
  • What I Learned Working On Time Tortoise in 2017
  • 2016 in Review
  • 2015 in Review
  • 2015 Summer Review

Archives

Recent Posts

  • Do Coding Bots Mean the End of Coding Interviews? December 31, 2024
  • Another Project for 2024 May 8, 2024
  • Dynamic Programming Wrap-Up May 1, 2024
  • LeetCode 91: Decode Ways April 24, 2024
  • LeetCode 70: Climbing Stairs April 17, 2024
  • LeetCode 221: Maximal Square April 10, 2024
  • Using Dynamic Programming for Maximum Product Subarray April 3, 2024
  • LeetCode 62: Unique Paths March 27, 2024
  • LeetCode 416: Partition Equal Subset Sum March 20, 2024
  • LeetCode 1143: Longest Common Subsequence March 13, 2024
Red-Green-Code
  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter
Copyright © 2025 Duncan Smith