Поделиться через


Using the Model-View-ViewModel (MVVM) pattern in a Windows Store business app using C#, XAML, and Prism

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

From: Developing a Windows Store business app using C#, XAML, and Prism for the Windows Runtime

Previous page | Next page

Learn how to implement the Model-View-ViewModel (MVVM) pattern in a Windows Store business app by using Prism for the Windows Runtime. This includes bootstrapping an MVVM app that uses Prism, using a view model locator to connect view models to views, and UI interaction using delegate commands and Blend behaviors.

Download

After you download the code, see Getting started using Prism for the Windows Runtime for instructions on how to compile and run the reference implementation, as well as understand the Microsoft Visual Studio solution structure.

You will learn

  • How to use dependency injection to decouple concrete types from the code that depends on the types.
  • How to bootstrap a Windows Store app that uses the MVVM pattern, by using a dependency injection container.
  • How to connect view models to views.
  • How a view is updated in response to changes in the underlying view model.
  • How to invoke commands and behaviors from views.

Applies to

  • Windows Runtime for Windows 8.1
  • C#
  • Extensible Application Markup Language (XAML)

Making key decisions

The MVVM pattern is well documented, and it brings benefits to many categories of apps. However, it is not always suited to every app. For example, using code-behind may be the best choice for small apps that have a limited life span. Nonetheless, if you choose to use the MVVM pattern to construct your app, you will have to make certain design decisions that will be difficult to change later on. Generally, these decisions are app-wide and their consistent use throughout the app will improve developer and designer productivity. The following list summarizes the decisions to make when implementing the MVVM pattern:

  • Should I use Prism to provide support for MVVM?
  • Should I use a dependency injection container?
    • Which dependency injection container should I use?
    • When is it appropriate to register and resolve components with a dependency injection container?
    • Should a component's lifetime be managed by the container?
  • Should the app construct views or view models first?
  • How should I connect view models to views?
    • Should I use XAML or code-behind to set the view's DataContext property?
    • Should I use a view model locator object?
    • Should I use an attached property to automatically connect view models to views?
    • Should I use a convention-based approach?
  • Should I expose commands from my view models?
  • Should I use behaviors in my views?
  • Should I include design time data support in my views?
  • Do I need to support a view model hierarchy?

Prism includes components to help accelerate the development of a managed Windows Store app that uses the MVVM pattern. It helps to accelerate development by providing core services commonly required by a Windows Store app, allowing you to focus on developing the user experiences for your app. Alternatively, you could choose to develop the core services yourself. For more info see Prism for the Windows Runtime reference.

There are several advantages to using a dependency injection container. First, a container removes the need for a component to locate its dependencies and manage their lifetime. Second, a container allows mapping of implemented dependencies without affecting the component. Third, a container facilitates testability by allowing dependencies to be mocked. Forth, a container increases maintainability by allowing new components to be easily added to the system.

In the context of a Windows Store app that uses the MVVM pattern, there are specific advantages to a dependency injection container. A container can be used for registering and resolving view models and views. In addition, a container can be used for registering services, and injecting them into view models. Also, a container can create the view models and inject the views.

There are several dependency injection containers available, with two common choices being Unity and MEF. Both Unity and MEF provide the same basic functionality for dependency injection, even though they work very differently. When considering which container to use, keep in mind the capabilities shown in the following table and determine which fits your scenario better.

Both containers Unity only MEF only
Register types and instances with the container. Resolves concrete types without registration. Recomposes properties and collections as new types are discovered.
Imperatively create instances of registered types. Resolves open generics. Automatically exports derived types.
Inject instances of registered types into constructors and properties. Uses interception to capture calls to objects and add additional functionality to the target object.
Have declarative attributes for marking types and dependencies that need to be managed.
Resolve dependencies in an object graph.

 

If you decide to use a dependency injection container, you should also consider whether it is appropriate to register and resolve components using the container. Registering and resolving instances from a container has a performance cost because of the container's use of reflection for creating each type, especially if components are being reconstructed for each page navigation in the app. If there are many or deep dependencies, the cost of creation can increase significantly. In addition, if the component does not have any dependencies or is not a dependency for other types, it may not make sense to put it in the container. Also, if the component has a single set of dependencies that are integral to the type and will never change, it may not make sense to put it in the container.

You should also consider whether a component's lifetime should be managed by the container. When you register a type the default behavior for the Unity container is to create a new instance of the registered type each time the type is resolved or when the dependency mechanism injects instances into other classes. When you register an instance the default behavior for the Unity container is to manage the lifetime of the object as a singleton. This means that the instance remains in scope as long as the container is in scope, and it is disposed when the container goes out of scope and is garbage-collected or when code explicitly disposes the container. If you want this singleton behavior for an object that Unity creates when you register types, you must explicitly specify the ContainerControlledLifetimeManager class when registering the type. For more info see Bootstrapping an MVVM Windows Store app Quickstart.

If you decide not to use a dependency injection container you can use the ViewModelLocator class, provided by the Microsoft.Practices.Prism.StoreApps library, to register view model factories for views, or infer the view model using a convention-based approach. For more info see Using the ViewModelLocator class to connect view models to views and Bootstrapping an MVVM Windows Store app Quickstart.

Deciding whether your app will construct views or the view models first is an issue of preference and complexity. With view first composition the app is conceptually composed of views which connect to the view models they depend upon. The primary benefit of this approach is that it makes it easy to construct loosely coupled, unit testable apps because the view models have no dependence on the views themselves. It's also easy to understand the structure of an app by following its visual structure, rather than having to track code execution in order to understand how classes are created and connected together. Finally, view first construction aligns better with the Windows Runtime navigation system because it is responsible for constructing the pages when navigation occurs, which makes a view model first composition complex and misaligned with the platform. View model first composition feels more natural to some developers, since the view creation can be abstracted away allowing them to focus on the logical non-UI structure of the app. However, this approach is often complex, and it can become difficult to understand how the various parts of the app are created and connected together. It can be difficult to understand the structure of an app constructed this way, as it often involves time spent in the debugger examining what classes gets created, when, and by whom.

The decision on how to connect view models to views is based on complexity, performance, and resilience:

  • If code-behind is used to connect view models to views it can cause problems for visual designers such as Blend and Visual Studio.
  • Using a view model locator object has the advantage that the app has a single class that is responsible for the instantiation of view models. The view model locator can also be used as a point of substitution for alternate implementations of dependencies, such as for unit testing or design time data.
  • A convention-based connection approach removes the need for much boilerplate code.
  • An attached property can be used to perform the connection automatically. This offers the advantage of simplicity, with the view having no explicit knowledge of the view model.

Note  The view will implicitly depend on specific properties, commands, and methods on the view model because of the data bindings it defines.

 

In Windows Store apps, you typically invoke some action in response to a user action, such as a button click that can be implemented by creating an event handler in the code-behind file. However, MVVM discourages placing code in the code-behind file as it's not easily testable because it doesn't maintain a good separation of concerns. If you wish to promote the testability of your app, by reducing the number of event handlers in your code-behind files, you should expose commands from your view models for ButtonBase-derived controls, and use behaviors in your views for controls that don't derive from ButtonBase, in order to connect them to view model exposed commands and actions.

If you will be using a visual designer to design and maintain your UI you'll need to include design time data support in your app so that you can view layouts accurately and see realistic results for sizing and styling decisions.

You should support a view model hierarchy if it will help to eliminate redundant code in your view model classes. If you find identical functionality in multiple view model classes, such as code to handle navigation, it should be refactored into a base view model class from which all view models classes will derive.

[Top]

MVVM in AdventureWorks Shopper

The AdventureWorks Shopper reference implementation uses the Unity dependency injection container. The Unity container reduces the dependency coupling between objects by providing a facility to instantiate instances of classes and manage their lifetime. During an object's creation, the container injects any dependencies that the object requires into it. If those dependencies have not yet been created, the container creates and resolves them first. For more info see Using a dependency injection container, Bootstrapping an MVVM Windows Store app Quickstart and Unity Container.

In the AdventureWorks Shopper reference implementation, views are constructed before view models. There is one view class per page of the UI (a page is an instance of the Windows.UI.Xaml.Controls.Page class), with design time data being supported on each view in order to promote the designer-developer workflow. For more info see Creating and navigating between pages.

Each view model is declaratively connected to a corresponding view using an attached property on a view model locator object to automatically perform the connection. View model dependencies are registered with the Unity dependency injection container, and resolved when the view model is created. A base view model class implements common functionality such as navigation and suspend/resume support for view model state. View model classes then derive from this base class in order to inherit the common functionality. For more info see Using the ViewModelLocator class to connect view models to views.

In order for a view model to participate in two-way data binding with the view, its properties must raise the PropertyChanged event. View models satisfy this requirement by implementing the INotifyPropertyChanged interface and raising the PropertyChanged event when a property is changed. Listeners can respond appropriately to the property changes when they occur. For more info see Updating a view in response to changes in the underlying view model or model.

The AdventureWorks Shopper reference implementation uses two options for executing code on a view model in response to interactions on a view, such as a button click or item selection. 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 will be executed. In addition to commands, behaviors can be attached to an object in the view and can listen for an event to be raised. In response, the behavior can then invoke an Action or an ICommand on the view model. For more info see UI interaction using the DelegateCommand class and Blend behaviors.

All of the view models in the AdventureWorks Shopper reference implementation 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. View models are connected to the model classes through model properties on the view model. However, if you want a strong separation between the model and the view models, you can package model classes in a separate library.

In the AdventureWorks Shopper Visual Studio solution there are two projects that contain the view, view model, and model classes:

  • The view classes are located in the AdventureWorks.Shopper project.
  • The view model and model classes are located in the AdventureWorks.UILogic project.

[Top]

What is MVVM?

MVVM is an architectural pattern that's a specialization of the presentation model pattern. It can be used on many different platforms and its intent is to provide a clean separation of concerns between the user interface controls and their logic. For more info about MVVM see MVVM Quickstart, Implementing the MVVM Pattern, Advanced MVVM Scenarios, and Developing a Windows Phone Application using the MVVM Pattern.

[Top]

Using a dependency injection container

Dependency injection enables decoupling of concrete types from the code that depends on these types. It uses a container that holds a list of registrations and mappings between interfaces and abstract types and the concrete types that implement or extend these types. The AdventureWorks Shopper reference implementation uses the Unity dependency injection container to manage the instantiation of the view model and service classes in the app.

Before you can inject dependencies into an object, the types of the dependencies need to be registered with the container. After a type is registered, it can be resolved or injected as a dependency. For more info see Unity.

In the AdventureWorks Shopper reference implementation, the App class instantiates the UnityContainer object and is the only class in the app that holds a reference to a UnityContainer object. Types are registered in the OnInitialize method in the App class.

[Top]

Bootstrapping an MVVM app using Prism's MvvmAppBase class

When you create a Windows Store app from a Visual Studio template, the App class derives from the Application class. In the AdventureWorks Shopper reference implementation, the App class derives from the MvvmAppBase class. The MvvmAppBase class provides support for suspension, navigation, settings, and resolving view types from view names. The App class derives from the MvvmAppBase class and provides app specific startup behavior.

The MvvmAppBase class, provided by the Microsoft.Practices.Prism.StoreApps library, is responsible for providing core startup behavior for an MVVM app, and derives from the Application class. The MvvmAppBase class constructor is the entry point for the app. The following diagram shows a conceptual view of how app startup occurs.

When deriving from the MvvmAppBase class, a required override is the OnLaunchApplication method from where you will typically perform your initial navigation to a launch page, or to the appropriate page based on a secondary tile launch of the app. The following code example shows how to override the OnLaunchApplication method in the App class, in order to respond to app activation from a tile or secondary tile.

AdventureWorks.Shopper\App.xaml.cs

protected override Task OnLaunchApplication(LaunchActivatedEventArgs args)
{
    if (args != null && !string.IsNullOrEmpty(args.Arguments))
    {
        // The app was launched from a Secondary Tile
        // Navigate to the item's page
        NavigationService.Navigate("ItemDetail", args.Arguments);
    }
    else
    {
        // Navigate to the initial page
        NavigationService.Navigate("Hub", null);
    }

    Window.Current.Activate();
    return Task.FromResult<object>(null);
}

This method navigates to the HubPage in the app, when the app launches normally, or the ItemDetailPage if the app is launched from a secondary tile. "Hub" and "ItemDetail" are specified as the logical names of the views that will be navigated to. The default convention specified in the MvvmAppBase class is to append "Page" to the name and look for that page in a .Views child namespace in the project. Alternatively, another convention can be specified by overriding the GetPageType method in the MvvmAppBase class. For more info see Handling navigation requests.

Note  The OnLaunchApplication method returns a Task, allowing it to launch a long running operation. If you don't have a long running operation to launch you should return an empty Task.

 

The app uses the Unity dependency injection container to reduce the dependency coupling between objects by providing a facility to instantiate instances of classes and manage their lifetime based on the configuration of the container. An instance of the container is created as a singleton in the App class, as shown in the following code example.

AdventureWorks.Shopper\App.xaml.cs

private readonly IUnityContainer _container = new UnityContainer();

The OnInitialize method in the MvvmAppBase class is overridden in the App class with app specific initialization behavior. For instance, this method should be overridden if you need to initialize services, or set a default factory or default view model resolver for the ViewModelLocator object. The following code example shows some of the OnInitialize method in the App class.

AdventureWorks.Shopper\App.xaml.cs

_container.RegisterInstance<INavigationService>(NavigationService);
_container.RegisterInstance<ISessionStateService>(SessionStateService);
_container.RegisterInstance<IEventAggregator>(EventAggregator);
_container.RegisterInstance<IResourceLoader>(new ResourceLoaderAdapter(new ResourceLoader()));

This code registers service instances with the container as singletons, based on their respective interfaces, so that the view model classes can take dependencies on them. This means that the container will cache the instances on behalf of the app, with the lifetime of the instances then being tied to the lifetime of the container.

A view model locator object is responsible for managing the instantiation of view models and their association to views. For more info see Using the ViewModelLocator class to connect view models to views. When the view model classes are instantiated the container will inject the dependencies that are required. If the dependencies have not yet been created, the container creates and resolves them first. This approach removes the need for an object to locate its dependencies or manage their lifetimes, allows swapping of implemented dependencies without affecting the object, and facilitates testability by allowing dependencies to be mocked.

[Top]

Using the ViewModelLocator class to connect view models to views

The AdventureWorks Shopper reference implementation uses a view model locator object to manage the instantiation of view models and their association to views. This has the advantage that the app has a single class that is responsible for the instantiation.

The ViewModelLocator class, in the Microsoft.Practices.Prism.StoreApps library, has an attached property, AutoWireViewModel that is used to associate view models with views. In the view's XAML this attached property is set to true to indicate that the view model should be automatically connected to the view, as shown in the following code example.

AdventureWorks.Shopper\Views\HubPage.xaml

prism:ViewModelLocator.AutoWireViewModel="true"

The AutoWireViewModel property is a dependency property that is initialized to false, and when its value changes the AutoWireViewModelChanged event handler is called. This method resolves the view model for the view. The following code example shows how this is achieved.

Microsoft.Practices.Prism.StoreApps\ViewModelLocator.cs

private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    FrameworkElement view = d as FrameworkElement;
    if (view == null) return; // Incorrect hookup, do no harm

    // Try mappings first
    object viewModel = GetViewModelForView(view);
    // Fallback to convention based
    if (viewModel == null)
    {
        var viewModelType = defaultViewTypeToViewModelTypeResolver(view.GetType());
        if (viewModelType == null) return;

        // Really need Container or Factories here to deal with injecting dependencies on construction
        viewModel = defaultViewModelFactory(viewModelType);
    }
    view.DataContext = viewModel;
}

The AutoWireViewModelChanged method first attempts to resolve the view model from any mappings that may have been registered by the Register method of the ViewModelLocator class. If the view model cannot be resolved using this approach, for instance if the mapping wasn't created, the method falls back to using a convention-based approach to resolve the correct view model type. This convention assumes that view models are in the same assembly as the view types, that view models are in a .ViewModels child namespace, that views are in a .Views child namespace, and that view model names correspond with view names and end with "ViewModel." For more info see Using a convention-based approach to connect view models to views. Finally, the method sets the DataContext property of the view type to the registered view model type.

Using a convention-based approach to connect view models to views

The AdventureWorks Shopper reference implementation redefines the convention for resolving view model types from view types in order to allow views and view models to reside in separate assemblies. This enables the business logic for the app to reside in a separate assembly that can be easily targeted for testing.

A convention-based approach to connecting view models to views removes the need for much boilerplate code. The convention used in AdventureWorks Shopper assumes that:

  1. View model types are located in a separate assembly from the view types.
  2. View model types are located in the AdventureWorks.UILogic assembly.
  3. View model type names append "ViewModel" to the view type names.

Using this convention, a view named HubPage will have a view model named HubPageViewModel. The following code example shows how the App class overrides the SetDefaultViewTypeToViewModelTypeResolver delegate in the ViewModelLocator class, to define how to resolve view model type names from view type names.

AdventureWorks.Shopper\App.xaml.cs

ViewModelLocator.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
    {
        var viewModelTypeName = string.Format(CultureInfo.InvariantCulture, "AdventureWorks.UILogic.ViewModels.{0}ViewModel, AdventureWorks.UILogic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=634ac3171ee5190a", viewType.Name);
        var viewModelType = Type.GetType(viewModelTypeName);
        return viewModelType;
    });

[Top]

Other approaches to constructing view models and views

There are many approaches that can be used to construct views and view models and associate them at runtime. The following sections describe three of these approaches.

Creating a view model declaratively

The simplest approach is for the view to declaratively instantiate its corresponding view model in XAML. When the view is constructed, the corresponding view model object will also be constructed. This approach can be demonstrated in the following code.

<Page.DataContext>
   <HubPageViewModel />
</Page.DataContext>

When the Page is created, an instance of the HubPageViewModel is automatically constructed and set as the view's data context. This approach requires your view model to have a default (parameter-less) constructor.

This declarative construction and assignment of the view model by the view has the advantage that it is simple and works well in design-time tools such as Blend and Visual Studio. The main disadvantage of this approach is that the view model requires a default constructor.

Creating a view model programmatically

A view can have code in the code-behind file that results in the view model being assigned to its DataContext property. This is often accomplished in the view's constructor, as shown in the following code example.

public HubPage()
{
   InitializeComponent();
   this.DataContext = new HubPageViewModel(NavigationService);
}

The programmatic construction and assignment of the view model within the view's code-behind has the advantage that it is simple and works well in design-time tools such as Blend and Visual Studio. The main disadvantage of this approach is that the view needs to provide the view model with any required dependencies.

Creating a view defined as a data template

A view can be defined as a data template and associated with a view model type. Data templates can be defined as resources, or they can be defined inline within the control that will display the view model. The content of the control is the view model instance, and the data template is used to visually represent it. This technique is an example of a situation in which the view model is instantiated first, followed by the creation of the view.

Data templates are flexible and lightweight. The UI designer can use them to easily define the visual representation of a view model without requiring any complex code. Data templates are restricted to views that do not require any UI logic (code-behind). Blend can be used to visually design and edit data templates.

The following example shows the AutoRotatingGridView custom control that is bound to a collection of ShoppingCartItemViewModels. Each object in the ShoppingCartItemViewModels collection is a view model instance. The view for each ShoppingCartItemViewModel is defined by the ItemTemplate property. The ShoppingCartItemTemplate specifies that the view for each ShoppingCartItemViewModel consists of a Grid containing multiple child elements, including an Image and several TextBlocks.

AdventureWorks.Shopper\Views\ShoppingCartPage.xaml

<awcontrols:AutoRotatingGridView x:Name="ShoppingCartItemsGridView"
                               x:Uid="ShoppingCartItemsGridView"
                               AutomationProperties.AutomationId="ShoppingCartItemsGridView"
                               SelectionMode="Single"
                               Width="Auto"
                               Grid.Row="2"
                               Grid.Column="1"
                               Grid.RowSpan="2"
                               VerticalAlignment="Top"
                               ItemsSource="{Binding ShoppingCartItemViewModels}"
                               SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                               ItemTemplate="{StaticResource ShoppingCartItemTemplate}"
                               MinimalItemTemplate="{StaticResource ShoppingCartItemTemplateMinimal}"
                               Margin="0,0,0,0">

For more info about the AutoRotatingGridView custom control see Creating a custom GridView control that responds to layout changes.

[Top]

Updating a view in response to changes in the underlying view model or model

All view model and model classes that are accessible to the view should implement the INotifyPropertyChanged interface. Implementing the INotifyPropertyChanged interface in your view model or model classes allows them to provide change notifications to any data-bound controls in the view when the underlying property value changes. However, this can be repetitive and error-prone. Therefore, the Microsoft.Practices.Prism.StoreApps library provides the BindableBase class that implements the INotifyPropertyChanged interface. The following code example shows this class.

Microsoft.Practices.Prism.StoreApps\BindableBase.cs

public abstract class BindableBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);

        return true;
    }

    protected void OnPropertyChanged(string propertyName)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Each view model class in the AdventureWorks Shopper reference implementation derives from the ViewModel base class that in turn derives from the BindableBase base class. Therefore, each view model class uses the SetProperty method in the BindableBase class to provide property change notification. The following code example shows how property change notification is implemented in a view model class in the AdventureWorks Shopper reference implementation.

AdventureWorks.UILogic\ViewModels\HubPageViewModel.cs

public IReadOnlyCollection<CategoryViewModel> RootCategories
{
    get { return _rootCategories; }
    private set { SetProperty(ref _rootCategories, value); }
}

For more info about data binding in the Windows Runtime, see Data binding overview.

Additional considerations when implementing property change notification

You should design your app for the correct use of property change notification. Here are some points to remember:

  • Never raise the PropertyChanged event during your object's constructor if you are initializing a property. Data-bound controls in the view cannot have subscribed to receive change notifications at this point.
  • Always implement the INotifyPropertyChanged interface on any view model or model classes that are accessible to the view.
  • Always raise a PropertyChanged event if a public property's value changes. Do not assume that you can ignore raising the PropertyChanged event because of knowledge of how XAML binding occurs. Such assumptions lead to brittle code.
  • Never use a public property's get method to modify fields or raise the PropertyChanged event.
  • Always raise the PropertyChanged event for any calculated properties whose values are used by other properties in the view model or model.
  • Never raise a PropertyChanged event if the property does not change. This means that you must compare the old and new values before raising the PropertyChanged event.
  • Never raise more than one PropertyChanged event with the same property name argument within a single synchronous invocation of a public method of your class. For example, suppose you have a Count property whose backing store is the _count field. If a method increments _count a hundred times during the execution of a loop, it should only raise property change notification on the Count property once after all the work is complete. For asynchronous methods you can raise the PropertyChanged event for a given property name in each synchronous segment of an asynchronous continuation chain.
  • Always raise the PropertyChanged event at the end of the method that makes a property change, or when your object is known to be in a safe state. Raising the event interrupts your operation by invoking the event's handlers synchronously. If this happens in the middle of your operation, you may expose your object to callback functions when it is in an unsafe, partially updated state. It is also possible for cascading changes to be triggered by PropertyChanged events. Cascading changes generally require updates to be complete before the cascading change is safe to execute.

[Top]

UI interaction using the DelegateCommand class and Blend behaviors

In Windows Store apps, you typically invoke some action in response to a user action (such as a button click) that can be implemented by creating an event handler in the code-behind file. However, in the MVVM pattern, the responsibility for implementing the action lies with the view model, and you should try to avoid placing code in the code-behind file.

Commands provide a convenient way to represent actions that can be easily bound to controls in the UI. They encapsulate the actual code that implements the action or operation and help to keep it decoupled from its actual visual representation in the view. The Windows Runtime includes controls that can be declaratively connected to a command. These controls will invoke the specified command when the user interacts with the control in a specific way.

Behaviors also allow you to connect a control to a command declaratively. However, behaviors can be used to invoke an action that is associated with a range of events raised by a control. Therefore, behaviors address many of the same scenarios as command-enabled controls, while providing a greater degree of flexibility and control. In addition, behaviors can also be used to associate command objects or methods with controls that were not specifically designed to interact with commands. For more info see Implementing behaviors to supplement the functionality of XAML elements.

Implementing command objects

View models typically expose command properties, for binding from the view, that are object instances that implement the ICommand interface. XAML inherently supports commands and ButtonBase-derived controls provide a Command property that can be data bound to an ICommand object provided by the view model. The ICommand interface defines an Execute method, which encapsulates the operation itself, and a CanExecute method, which indicates whether the command can be invoked at a particular time. The Microsoft.Practices.Prism.StoreApps library provides the DelegateCommand class to implement commands.

The AdventureWorks Shopper reference implementation uses the DelegateCommand class that encapsulates two delegates that each reference a method implemented within your view model class. It inherits from the DelegateCommandBase class that implements the ICommand interface’s Execute and CanExecute methods by invoking these delegates. You specify the delegates to your view model methods in the DelegateCommand class constructor, which is defined as follows.

Microsoft.Practices.Prism.StoreApps\DelegateCommand.cs

public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
    : base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))
{
    if (executeMethod == null || canExecuteMethod == null)
        throw new ArgumentNullException("executeMethod");
}

For example, the following code shows how a DelegateCommand instance, which represents a sign in command, is constructed by specifying delegates to the SignInAsync and CanSignIn view model methods. The command is then exposed to the view through a read-only property that returns a reference to an ICommand.

AdventureWorks.UILogic\ViewModels\SignInFlyoutViewModel.cs

public DelegateCommand SignInCommand { get; private set; }

AdventureWorks.UILogic\ViewModels\SignInFlyoutViewModel.cs

SignInCommand = DelegateCommand.FromAsyncHandler(SignInAsync, CanSignIn);

The DelegateCommand class is a generic type. The type argument specifies the type of the command parameter passed to the Execute and CanExecute methods. A non-generic version of the DelegateCommand class is also provided for use when a command parameter is not required.

When the Execute method is called on the DelegateCommand object, it simply forwards the call to the method in the view model class via the delegate that you specified in the constructor. Similarly, when the CanExecute method is called, the corresponding method in the view model class is called. The delegate to the CanExecute method in the constructor is optional. If a delegate is not specified, the DelegateCommand will always return true for CanExecute.

The view model can indicate a change in the command’s CanExecute status by calling the RaiseCanExecuteChanged method on the DelegateCommand object. This causes the CanExecuteChanged event to be raised. Any controls in the UI that are bound to the command will update their enabled status to reflect the availability of the bound command.

Invoking commands from a view

Any controls that derive from ButtonBase, such as Button or HyperlinkButton, can be easily data bound to a command through the Command property. The following code example shows how the SubmitButton in the SignInFlyout binds to the SignInCommand in the SignInFlyoutViewModel class.

AdventureWorks.Shopper\Views\SignInFlyout.xaml

<Button x:Uid="SubmitButton"
                    x:Name="SubmitButton"
                    Background="{StaticResource AWShopperAccentBrush}"
                    Content="Submit"
                    HorizontalAlignment="Stretch"
                    Foreground="{StaticResource AWShopperButtonForegroundBrush}"
                    Margin="0,25,0,0"
                    Command="{Binding SignInCommand}"
                    AutomationProperties.AutomationId="SignInSubmitButton"/>

A command parameter can also be optionally defined using the CommandParameter property. The type of the expected argument is specified in the Execute and CanExecute target methods. The control will automatically invoke the target command when the user interacts with that control, and the command parameter, if provided, will be passed as the argument to the command’s Execute method.

Implementing behaviors to supplement the functionality of XAML elements

A behavior allows you to add functionality to a XAML element by writing that functionality in a behavior class and attaching it to the element as if it was part of the element itself. Behaviors enable you to implement code that you would normally have to write as code-behind because it directly interacts with the API of XAML elements, in such a way that it can be concisely attached to a XAML element and packaged for reuse across more than one view or app. In the context of MVVM, behaviors are a useful approach for connecting items that are occurring in the view due to user interaction, with the execution in a view model.

A behavior that is attached to a XAML element through attached properties is known as an attached behavior. The behavior can then use the exposed API of the element to which it is attached to add functionality to that element or other elements in the visual tree of the view. For more info see Dependency properties overview, Attached properties overview, and Custom attached properties. The AdventureWorks Shopper reference implementation does not contain any attached behaviors.

Blend includes a variety of built-in behaviors, which are known as Blend behaviors. These behaviors can be reused in Windows Store apps through the Behaviors SDK. The SDK supports adding existing behaviors and actions to Windows Store apps, and creating new ones. A Blend behavior adds some behavior to a XAML element, with an action adding functionality that’s invoked when a condition is met, such as an event being raised. Collectively, behaviors and actions are known as interactions.

The AdventureWorks Shopper reference implementation uses a number of interactions from the Behaviors SDK and also includes custom behaviors. To create a new behavior you should create a class that derives from the DependencyObject class, and implements the IBehavior interface. In the AdventureWorks Shopper reference implementation this functionality is provided by the Behavior<T> class. This class provides an AssociatedObject property that gives a reference to the element to which the behavior is attached, and Attach and Detach methods. Each custom behavior then derives from the Behavior<T> class, overriding the OnAttached and OnDetached abstract methods to provide logic that will be executed when the behavior is attached and detached from XAML elements. The following code example shows the ComboBoxKeyboardSelection behavior used by the AdventureWorks Shopper reference implementation to select the ComboBoxItem that starts with the key pressed by the user.

AdventureWorks.Shopper\Behaviors\ComboBoxKeyboardSelection.cs

public class ComboBoxKeyboardSelection : Behavior<ComboBox>
{
    protected override void OnAttached()
    {
        ComboBox comboBox = this.AssociatedObject;

        if (comboBox != null)
        {
            comboBox.KeyUp += comboBox_KeyUp;
        }
    }

    private void comboBox_KeyUp(object sender, KeyRoutedEventArgs e)
    {
        var comboBox = (ComboBox)sender;
        foreach (var item in comboBox.Items)
        {
            var comboBoxItemValue = item as ComboBoxItemValue;
            if (comboBoxItemValue != null && comboBoxItemValue.Value.StartsWith(e.Key.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                comboBox.SelectedItem = comboBoxItemValue;
                return;
            }
        }
    }
    protected override void OnDetached()
    {
        ComboBox comboBox = this.AssociatedObject;

        if (comboBox != null)
        {
            comboBox.KeyUp -= comboBox_KeyUp;
        }
    }
}

The OnAttached and OnDetached methods are simply used to register and deregister a method for the KeyUp event. The event handler method selects the ComboBoxItem that starts with the key pressed by the user.

One of the interactions from the Behaviors SDK that is used by the AdventureWorks Shopper reference implementation is the NavigateToPageAction interaction, which invokes navigation to a specific page in the app. For instance, when the shopping cart icon is selected in the top app bar the NavigateToPageAction interaction is used to navigate to the ShoppingCartPage, as shown in the following code example.

AdventureWorks.Shopper\Views\TopAppBarUserControl.xaml

<Button x:Uid="ShoppingCartAppBarButton" x:Name="ShoppingCartAppBarButton" 
    AutomationProperties.AutomationId="ShoppingCartAppBarButton"
    Margin="0,0,5,0" 
    Height="125"
    Style="{StaticResource CartStyle}" 
    Content="Shopping Cart">
    <Interactivity:Interaction.Behaviors>
        <Core:EventTriggerBehavior EventName="Click">
            <Core:NavigateToPageAction TargetPage="AdventureWorks.Shopper.Views.ShoppingCartPage"/>
        </Core:EventTriggerBehavior>
    </Interactivity:Interaction.Behaviors>
</Button>

The EventTriggerBehavior binds the Click event of the Button to the NavigateToPageAction. So when the Button is selected the NavigateToPageAction is executed, which navigates to the ShoppingCartPage. The NavigateToPageAction interaction also allows a Parameter to be specified. However, it is not currently possible to specify the event arguments that are associated with the Click event in the Parameter property. To solve this problem we created the NavigateWithEventArgsToPageAction that invokes navigation to a specified page, and allows the event arguments to be passed as a parameter to the page being navigated to.

AdventureWorks.Shopper\Behaviors\NavigateWithEventArgsToPageAction.cs

public class NavigateWithEventArgsToPageAction : DependencyObject, IAction
{
    public string TargetPage { get; set; }
    public string EventArgsParameterPath { get; set; }
    object IAction.Execute(object sender, object parameter)
    {
        //Walk the ParameterPath for nested properties.
        var propertyPathParts = EventArgsParameterPath.Split('.');
        object propertyValue = parameter;
        foreach (var propertyPathPart in propertyPathParts)
        {
            var propInfo = propertyValue.GetType().GetTypeInfo().GetDeclaredProperty(propertyPathPart);
            propertyValue = propInfo.GetValue(propertyValue);    
        }

        var pageType = Type.GetType(TargetPage);
        
        var frame = GetFrame(sender as DependencyObject);
        return frame.Navigate(pageType, propertyValue);
    }

    private Frame GetFrame(DependencyObject dependencyObject)
    {
        var parent = VisualTreeHelper.GetParent(dependencyObject);
        var parentFrame = parent as Frame;
        if (parentFrame != null) return parentFrame;
        return GetFrame(parent);
    }
}

To create a new action you must create a class that derives from the DependencyObject class, and implements the IAction interface. The IAction interface has only one method that needs to be implemented, named Execute. Here, the Execute method traverses the visual tree to obtain the Frame control used by the current page, and then calls its Navigate method to navigate to the target page, passing in the specified parameter.

Invoking behaviors from a view

Behaviors are particularly useful if you want to attach a method to a control that does not derive from ButtonBase. For example, the AdventureWorks Shopper reference implementation uses the NavigateWithEventArgsToPageAction interaction to enable the ItemClick event of the MultipleSizedGridView control to invoke page navigation.

AdventureWorks.Shopper\Views\HubPage.xaml

<awcontrols:MultipleSizedGridView x:Name="itemsGridView"
                                AutomationProperties.AutomationId="HubPageItemGridView"
                                AutomationProperties.Name="Grouped Items"
                                Margin="0,0,0,0"
                                Padding="120,0,40,46"
                                ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
                                ItemTemplate="{StaticResource AWShopperItemTemplate}"
                                MinimalItemTemplate="{StaticResource ProductTemplateMinimal}"
                                SelectionMode="None"
                                ScrollViewer.IsHorizontalScrollChainingEnabled="False"
                                IsItemClickEnabled="True"
                                Loaded="itemsGridView_Loaded">
    <interactivity:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="ItemClick">
            <awbehaviors:NavigateWithEventArgsToPageAction TargetPage="AdventureWorks.Shopper.Views.ItemDetailPage"
                                                                EventArgsParameterPath="ClickedItem.ProductNumber" />
        </core:EventTriggerBehavior>
    </interactivity:Interaction.Behaviors>

The EventTriggerBehavior binds the ItemClick event of the MultipleSizedGridView to the NavigateWithEventArgsToPageAction. So when a GridViewItem is selected the NavigateWithEventArgsToPageAction is executed, which navigates from the HubPage to the ItemDetailPage, passing in the ProductNumber of the ClickedItem to the ItemDetailPage.

[Top]

Additional MVVM considerations

Here are some additional considerations when applying the MVVM pattern to Windows Store apps in C#.

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, or with visual states.

Keep views and view models independent

The binding of views to a particular property in its data source should be a view's principal dependency on its corresponding view model. In particular, do not reference view types or the Windows.Current object from view models. If you follow the principles we outlined here, you will have the ability to test view models in isolation, and reduce the likelihood of software defects by limiting scope.

Use asynchronous programming techniques to keep the UI responsive

Windows Store apps are about a fast and fluid user experience. For that reason the AdventureWorks Shopper reference implementation keeps the UI thread unblocked. AdventureWorks Shopper uses asynchronous library methods for I/O operations and raises events to asynchronously notify the view of a property change.

[Top]