Aracılığıyla paylaş


Using the Model-View-ViewModel (MVVM) pattern in Hilo (Windows Store apps using C++ and XAML)

From: Developing an end-to-end Windows Store app using C++ and XAML: Hilo

Previous page | Next page

Early in the project we decided to adopt the Model-View-ViewModel (MVVM) pattern for Hilo's architecture. We were attracted by the fact that the MVVM pattern makes it easier to maintain and test your Windows Store app using C++ and XAML, especially as it grows. MVVM is a relatively new pattern for C++ apps.

Download

After you download the code, see Getting started with Hilo for instructions.

You will learn

  • How Windows Store apps can benefit from MVVM.
  • Recommended techniques for applying the MVVM pattern to Windows Store apps.
  • How to map views to UI elements.
  • How to share view models across views.
  • How to execute commands in a view model.

Applies to

  • Windows Runtime for Windows 8
  • Visual C++ component extensions (C++/CX)
  • XAML

What is MVVM?

MVVM is an architectural pattern. It is a specialization of the presentation model pattern that was introduced by Martin Fowler. It is also related to the model-view-controller pattern (MVC) and the model view presenter (MVP) pattern that you may already know.

An app that uses MVVM separates business logic, UI, and presentation behavior.

  • Models represent the state and operations of business objects that your app manipulates. For example, Hilo reads and modifies image files, so it makes sense that data types for image files and operations on image files are part of Hilo’s model.
  • Views contain UI elements, and they include any code that implements the app’s user experience. A view defines the structure, layout, and appearance of what the user sees on the screen. Grids, pages, buttons, and text boxes are examples of the elements that view objects manage.
  • View models encapsulate the app’s state, actions, and operations. A view model serves as the decoupling layer between the model and the view. It provides the data in a format that the view can consume and updates the model so that the view does not need to interact with the model. View models respond to commands and trigger events. They also act as data sources for any data that views display. View models are built specifically to support a view. You can think of a view model as the app, minus the UI. In Windows Store apps, you can declaratively bind views to their corresponding view models.

Here are the relationships between a view, a view model and a model.

[Top]

MVVM in Hilo

In Hilo, there is one view class per page of the UI. (A page is an instance of the Windows::UI::Xaml::Controls::Page class.) Each view has a corresponding view model class. All of the view models in Hilo share the app's domain model, which is often just called the model. The model consists of classes that the view models use to implement the app’s functionality.

Note  If you want to jump directly to a walkthrough of the Hilo’s view and view model code, see Creating and navigating pages in Hilo.

 

In the Visual Studio solution Hilo.sln there are solution folders that are named for each of the MVVM layers.

  • The Models folder contains .cpp (C++) and .h (C++ header) files that make up the Hilo model.
  • The Views folder contains the UI classes and XAML files.
  • The ViewModels folder contains .cpp and .h files for the app’s view model classes.

With the MVVM pattern, XAML data binding lets a view model act as a page's data context. A data context is responsible for providing properties that provide the view with data for the UI elements on the page.

Note  You don’t have to use data binding to connect views and view models. It’s also possible to use a code-behind file containing C++ code that is associated with page classes. You can recognize code-behind files because they use the .xaml.cpp suffix. For example, in Hilo the file MainHubView.xaml.cpp is the code-behind file for the page that the MainHubView.xaml file defines. Many visual design tools such as Microsoft Expression are optimized for use with data binding.

 

View models connect to the app’s underlying model by calling instance methods. You need no special binding to make these calls. If you want a strong separation between the model and the app's view models, you can package model classes in a separate library. Hilo doesn't use a separate library for its model. Instead, it just keeps the files that define model classes in a separate folder in the Hilo Visual Studio project.

[Top]

Why use MVVM for Hilo?

There are two main implementation approaches for a UI: using a code-behind file for the presentation logic, or separating UI structure and presentation logic with a pattern such as the MVVM pattern. After analyzing our needs, we chose the MVVM approach for Hilo because:

  • We wanted to test our presentation logic. MVVM makes it easy to cleanly separate view logic from UI controls, which is important for test automation.
  • We wanted to make sure that the view and presentation logic could evolve independently and reduce dependencies between UX designers and developers. MVVM, used with XAML’s data binding, makes this possible.

[Top]

For more info

You can find more info about MVVM online. Here are some examples in managed code, but the concepts also apply to C++:

[Top]

Variations of the MVVM pattern

You can customize the MVVM pattern in various ways. Let's look at some of them.

  • Mapping views to UI elements other than pages
  • Sharing view models among multiple views
  • Executing commands in a view model
  • Using a view model locator object to bind views to view models

Mapping views to UI elements other than pages

In Hilo, each page class is a MVVM view object, and all MVVM views are pages. But you don't have to do the same. For example, a view could be a DataTemplate for an object in an ItemsControl.

Sharing view models among multiple views

A view can have its own view model, or it can share the view model of another view. The choice depends on whether views share a lot of common functionality. In Hilo, each view is associated with a unique view model for simplicity.

Executing commands in a view model

You can use data binding for buttons and other UI controls that cause the app to perform operations. If the control is a Command Source, the control’s Command property is data-bound to an ICommand property on the view model. When the control’s command is invoked, the code in the view model is executed. We recommend that you use data binding for commands when you use MVVM.

Here is an example of executing a command from Hilo. The rotate image page contains a UI element for the Save File button. This XAML code comes from the RotateImageView.xaml file.

<Button x:Name="SaveButton"
        x:Uid="AcceptAppBarButton"
        Command="{Binding SaveCommand}" 
        Style="{StaticResource AcceptAppBarButtonStyle}"
        Tag="Save" />

The expression "Command={Binding SaveCommand}" creates a binding between the Command property of the button and the SaveCommand property of the RotateImageViewModel class. The SaveCommand property contains a handle to an ICommand object. The next code comes from the RotateImageViewModel.cpp file.

ICommand^ RotateImageViewModel::SaveCommand::get()
{
    return m_saveCommand;
}

The m_saveCommand member variable is initialized in the constructor of the RotateImageViewModel class. Here is the code from the RotateImageViewModel.cpp file.

m_saveCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &RotateImageViewModel::SaveImage), nullptr);

Hilo's DelegateCommand class is an implementation of the ICommand interface. The class defines the ExecuteDelegate delegate type. Delegates let you use a pointer to a C++ member function as a callable Windows Runtime object. Hilo invokes the ExecuteDelegate when the UI triggers the command.

Note  For more info about the delegate language extension in C++/CX, see Delegates (C++/CX).

 

Because we used data binding, changing the action of the save command is a matter of assigning a different DelegateCommand object to the m_saveCommand member variable. There's no need to change the view's XAML file.

In this example, the underlying member function comes from the RotateImageViewModel.cpp file.

void RotateImageViewModel::SaveImage(Object^ parameter)
{
   // Asynchronously save image file
}     

Using a view model locator object to bind views to view models

In Hilo, each view (page) has a corresponding view model.

If you use MVVM, the app needs to connect its views to its view models. This means that each view must have a view model assigned to its DataContext property. For Hilo, we used a single ViewModelLocator class because we needed set up code to execute before UI elements were bound to the view model. The ViewModelLocator class has properties that retrieve a view model object for each page of the app. See Creating and navigating between pages for a description of how the ViewModelLocator class binds views and view models in Hilo.

You don't have to use a view model locator class. In fact, there are several ways to bind a view to its corresponding view model object. If you don’t use a view model locator class, you can connect the creation and destruction of view model instances to the lifetime of the corresponding view object. For example, you could create a new view model instance every time the page loads.

You can also connect views to view models in a code-behind file. The code in a code-behind file can instantiate a new view model instance and assign it to the view’s DataContext property. You can instantiate the view model in the page's Initialize method or in its OnNavigatedTo method.

[Top]

Tips for designing Windows Store apps using MVVM

Here are some tips for applying the MVVM pattern to Windows Store apps in C++.

  • Keep view dependencies out of the view model.
  • Centralize data conversions in the view model or a conversion layer.
  • Expose operational modes in the view model.
  • Ensure that view models have the Bindable attribute.
  • Ensure that view models implement the INotifyProperyChanged interface for data binding to work.
  • Keep views and view models independent.
  • Use asynchronous programming techniques to keep the UI responsive.
  • Always observe threading rules for Windows Runtime objects.

Keep view dependencies out of the view model

When designing a Windows Store app with the MVVM pattern, you need to decide what is placed in the model, what is placed in the views, and what is placed in the view models. This division is often a matter of taste, but some general principles apply. Ideally, you define the view with XAML, with only limited code-behind that doesn't contain business logic. We also recommend that you keep the view model free of dependencies on the data types for UI elements or views. Don't include view header files in view model source files.

Centralize data conversions in the view model or a conversion layer

The view model provides data from the model in a form that the view can easily use. To do this the view model sometimes has to perform data conversion. Placing this data conversion in the view model is a good idea because it provides properties in a form that the UI can bind to.

It is also possible to have a separate data conversion layer that sits between the view model and the view. This might occur, for example, when data types need special formatting that the view model doesn’t provide.

Expose operational modes in the view model

The view model may also be responsible for defining logical state changes that affect some aspect of the display in the view, such as an indication that some operation is pending or whether a particular command is available. You don't need code-behind to enable and disable UI elements—you can achieve this by binding to a view model property.

Here's an example.

<Grid Background="{Binding HasPhotos, Converter={StaticResource BrushConverter}}"
      Height="150"
      IsTapEnabled="{Binding HasPhotos}"
      PointerEntered="OnZoomedOutGridPointerEntered"
      Margin="0"
      Width="150">

In the example the IsTapEnabled attribute is bound to the view model's HasPhoto property.

Ensure that view models have the Bindable attribute

For the view model to participate in data binding with the view, view model classes must have the Windows::UI::Xaml::Data::Bindable attribute to ensure that the type is included in XAML’s generated file.

In addition, you also need to include the header for your view model in an App.xaml.h header file, either directly or indirectly. In Hilo all the view models header files are included in the ViewModelLocator.h file, which is included in the App.xaml.h file. This ensures that the types necessary to work with XAML get properly generated at compile time.

Note  For more info about the attribute language extension of C++/CX, see User-defined attributes (C++/CX).

 

Here's is an example of the Bindable attribute.

[Windows::UI::Xaml::Data::Bindable] 
[Windows::Foundation::Metadata::WebHostHiddenAttribute]
public ref class MainHubViewModel sealed : public ViewModelBase 
{ 
  // ...  
}     

Ensure that view models implement the INotifyProperyChanged interface for data binding to work

View models that need to notify clients that a property value has changed must raise the PropertyChanged event. To do this, view model classes need to implement the Windows::UI::Xaml::Data::INotifyPropertyChanged interface. The Windows Runtime registers a handler for this event when the app runs. Visual Studio provides an implementation of the INotifyPropertyChanged interface in the BindableBase template class that you can use as a base class for any XAML data source. Here is the generated header file, from BindableBase.h:

[Windows::Foundation::Metadata::WebHostHidden]
public ref class BindableBase : Windows::UI::Xaml::DependencyObject, Windows::UI::Xaml::Data::INotifyPropertyChanged, Windows::UI::Xaml::Data::ICustomPropertyProvider
{
  public:
    virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;

   // ...

  protected:
    virtual void OnPropertyChanged(Platform::String^ propertyName);
};    

The generated implementation invokes the handler when the event is raised.

void BindableBase::OnPropertyChanged(String^ propertyName)
{
    PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}     

Note  C++/CX has events and properties as part of the programming language. It includes the event and property keywords. Windows Runtime types declare events in their public interfaces that your app can subscribe to. The subscriber performs custom actions when the publisher fires the event. For more info about C++/CX features that support the Windows Runtime, see Creating Windows Runtime Components in C++. For more info about the event language extension of C++/CX that is used in this code example, see Events (C++/CX).

 

View model classes can inherit the INotifyPropertyChanged implementation by deriving from the BindableBase class. For example, here is the declaration of the ViewModelBase class in Hilo. The code comes from the ViewModelBase.h file.

public ref class ViewModelBase : public Common::BindableBase
{
  // ...
}     

Whenever view models need to tell the UI that a bound property has changed, they call the OnPropertyChanged method that they inherited from the BindableBase class. For example, here is a property set method defined in the RotateImageViewModel class.

void RotateImageViewModel::RotationAngle::set(float64 value)
{
    m_rotationAngle = value;

    // Derive margin so that rotated image is always fully shown on screen.
    Thickness margin(0.0);
    switch (safe_cast<unsigned int>(m_rotationAngle))
    {
    case 90:
    case 270:
        margin.Top = 110.0;
        margin.Bottom = 110.0;
        break;
    }
    m_imageMargin = margin;
    OnPropertyChanged("ImageMargin");
    OnPropertyChanged("RotationAngle");
}

Note  Property notifications to XAML must occur in the UI thread. This means that the OnPropertyChanged method and any of its callers must also occur in the app’s UI thread. In general, a useful convention is that all view model methods and properties must be called in the app’s UI thread.

 

Keep views and view models independent

If you follow the principles we outlined here, you will be able to re-implement a view model without any change to the view. The binding of views to a particular property in its data source should be a view's principal dependency on its corresponding view model. (If you rename a bound property in the view model you need to rename it in the XAML data binding expression as well.)

Use asynchronous programming techniques to keep the UI responsive

Windows Store apps are about a fast and fluid user experience. For that reason Hilo keeps the UI thread unblocked. Hilo uses asynchronous library methods for I/O operations and parallel tasks when operations perform a significant amount of computation. Hilo raises events to asynchronously notify the view of a property change.

See Asynchronous programming for Windows Store apps using C++ and XAML for more info.

Always observe threading rules for Windows Runtime objects

Objects that are created by calls into the Windows Runtime are sometimes single-threaded. This means that you must invoke the methods, properties and event handlers from the thread context that was used to create the object. In most cases the context is the app’s UI thread.

To avoid errors, Hilo was designed so that calls into its view models occur on the app’s UI thread. (Model classes perform time-consuming operations such as image processing on worker threads.)

See Programming patterns for asynchronous UI for more info. See Controlling the Execution Thread for info about the threading model that Windows Store apps use.

For a walkthrough of Hilo's use of asynchronous programming, see Asynchronous programming for Windows Store apps using C++ and XAML.

[Top]