Windows 8 Store Apps - Embracing the MVVM Pattern (Part 2)

02 February 2013

Summary:
In this post I continue to introduce the use of the Model-View-ViewModel pattern when developing Windows 8 Store apps. This is part two of my introduction to MVVM with Windows Store apps. Part one can be found here.

Introduction

In part one of this post we looked at:

  1. How the MVVM pattern facilitates a separation-of-concerns between the logical layers of an app:

    • View: contains UI-related mark-up (XAML)
    • ViewModel: makes model data available to the view (via data-binding) and responds to commands from the view
    • Model: encapsulates domain logic and data
  2. How to set the View's DataContext to point to the ViewModel
  3. How to use data-binding to move data between View and ViewModel (in both directions)
  4. How the ViewModel raises change notifications that can be consumed by the View, allowing it to respond to changes in the ViewModel
  5. How to send commands from the View to the ViewModel by binding the Command property of controls to ICommand-derived RelayCommand properties in the ViewModel

In this post we examine how to tackle the issue that not all controls have appropriate Command properties, and certainly not for multiple types of command we might wish to send. For example, suppose we want a ListView (which doesn't have a Command property) to send commands to the ViewModel for both the SelectionChanged and Holding events.

Dependency Properties and Attached Properties

The mechanism which allows us to "add" command properties to controls is based on Dependency Properties and Attached Properties. So, before looking at their application in MVVM, let's review how dependency properties and attached properties work.

Dependency Properties

As you're probably aware, dependency properties are a major architectural component of the WPF and WinRT frameworks. It is dependency properties which allow you, for instance, to set a Rectangle.Fill property with Fill = "Red" in XAML, even though the following code shows that in reality a SolidColorBrush object is what's required:

<Rectangle Height="100" Width="300" Fill="Red" />
var redRect = new Rectangle();
redRect.Fill = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0));

In the above XAML example, the Fill dependency property coerces the string "Red" into the necessary SolidColorBrush object.

To summarize, dependency properties:

  • Act like normal (CLR) properties (where a value is mapped to a private field in the object), but are actually stored in a dictionary in the the DependencyObject base class (i.e. dependency properties do not represent private fields in an object)
  • Values are read/written by calls to the GetValue()/SetValue() methods inherited from DependencyObject - you can also get/set values as if they were CLR properties (e.g. myObj.myDp = value), provided the CLR getter/setter calls GetValue(dp)/SetValue(dp)
  • Values are resolved dynamically through calls to the GetValue() - rules of precedence determine the actual value returned (things like animations, default values, styles, inheritance, etc. affect the value)
  • Calls to the SetValue() method set the local value of the property - this may not be the value returned by a call to GetValue() (rules of precedence may dictate that a value other than the local value is returned)
  • Act like "calculated fields" - wrappers for values calculated from other properties
  • Can participate in property value inheritance - if their value isn't explicitly set, they can inherit a value from an element higher up the logical tree
  • Are efficient - they store only modified (non-default) values (XAML elements have LOTS of properties - expensive if implemented with normal CLR properties)
  • Can provide validation, and default values, coerce values into the required type, and raise change notifications

The following code snippet illustrates some of the above points:

var rect = new Rectangle();

var opacity = rect.Opacity;  // = 1.0. Default value
var opacity2 = rect.GetValue(OpacityProperty);  // = 1.0. Directly equivalent to the above statement

rect.Opacity = 0.5;  // Set the local value
opacity = rect.Opacity; // = 0.5

rect.ClearValue(OpacityProperty);  // Clears the local value 
opacity = rect.Opacity;  // = 1.0. Gets the default value

Create your own Dependency Properties

The following shows how to create a dependency property named BallColor for a custom Ball class. Notice that all we need to do is obey the convention that dictates that if our CLR property is named XYZ, we register a public static readonly DependencyProperty called XYZProperty, using the static DependencyProperty.Register() method:

public class Ball : DependencyObject
{
    // Define a CLR property with getter/setter accessors for the GetValue()/SetValue() methods
    public Color BallColor
    {
        // Get the value by calling the GetValue method inherited from DependencyObject 
        get { return (Color) GetValue(BallColorProperty); }

        // Set the value by calling the SetValue method inherited from DependencyObject 
        set { SetValue(BallColorProperty, value); }  
    }

    // Register the dependency property
    public static readonly DependencyProperty BallColorProperty = DependencyProperty.Register(
        "BallColor",                        // Name of the CLR property
        typeof(Color),                      // Property type
        typeof(Ball),                       // Owning type
        new PropertyMetadata(
            Color.FromArgb(255, 255, 0, 0), // Default value (red)
            (source, args) =>               // Handle value changed
            {
                
            }));  
}

For more details on dependency properties, take a look at the MSDN documentation.

Attached Properties

Attached properties are an extension of the dependency property concept and provide a mechanism for "adding" (attaching) properties to elements that don't actually contain those properties.

For example, the following XAML illustrates the concept:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    
    <!-- 
        TextBlock doesn't contain a Grid.Column property - here we use the
        concept of attached properties to set the column to "1"
    -->
    <TextBlock x:Name="HelloTextBlock" Text="Hello" Grid.Column="1"/>
</Grid>

Create your own Attached Properties

Here we see how attached properties are defined by adding public static GetBallColor() and SetBallColor() methods to our Ball class (the convention is that if the CLR property is named XYZ, we added static methods named GetXYZ() and SetXYZ()) :

public class Ball : DependencyObject
{
    public Color BallColor
    {
        // Get the value by calling the GetValue method inherited from dependency property 
        get { return (Color) GetValue(BallColorProperty); }

        // Set the value by calling the SetValue method inherited from dependency property 
        set { SetValue(BallColorProperty, value); }  
    }

    // Register the dependency property
    public static readonly DependencyProperty BallColorProperty = DependencyProperty.Register(
        "BallColor",                        // Name of the CLR property
        typeof(Color),                      // Property type
        typeof(Ball),                       // Owning type
        new PropertyMetadata(
            Color.FromArgb(255, 255, 0, 0), // Default value (red)
            (source, args) =>               // Handle value changed
            {
                
            }));

    // Define an Attached Property by adding static Set{DependencyProperty} and 
    // Get{DependencyProperty} methods
    public static Color GetBallColor(UIElement element)
    {
        // element is the UIElement to which this property will be attached
        return (Color)element.GetValue(BallColorProperty);
    }
    
    public static void SetBallColor(UIElement element, Color color)
    {
        element.SetValue(BallColorProperty, color);
    }
}

You can get/set attached properties from code (they're just static methods), although it's much more commonly a XAML technique:

var rect = new Rectangle();

// Access an attached property from code...
Ball.SetBallColor(rect, Color.FromArgb(255, 255, 0, 0));
var color = Ball.GetBallColor(rect);

For more details on attached properties, take a look at the MSDN documentation.

Command Adapters

We're now ready to look at how to use attached properties to add commands to UI elements. In the following example we have a ListView which is bound to an ObservableCollection<string> in the ViewModel. You can add items to the list by either tapping the "Add Default Item" button, or by clicking one of the Red, Green or Blue Rectangle elements. We use attached properties to add commands to the rectangles, so that clicking them fires a command which is handled in the ViewModel. Here's what the UI looks like:

And here's the View defined in XAML (the code-behind file only contains the default constructor which calls InitializeComponent()):

<Page
    x:Class="MvvmDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MvvmDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <!-- 
        Set the overall DataContext for the page to be the ViewModel...
    -->
    
    <Page.DataContext>
        <local:ViewModel />
    </Page.DataContext>
    
    <Page.Resources>
        <DataTemplate x:Key="ItemDataTemplate">
            <TextBlock FontSize="32" Foreground="Gold" Margin="5" Text="{Binding}"/>
        </DataTemplate>
    </Page.Resources>
    
    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Width="500" Margin="20">

            <TextBlock FontSize="28" HorizontalAlignment="Center" Text="Selected item:"/>
            <TextBlock 
                FontSize="28" 
                Margin="10" 
                HorizontalAlignment="Center" 
                Foreground="Gold" 
                Text="{Binding SelectedDataItem}"/>
            
            <!-- 
                Button directly supports firing a Command, so bind it to the ButtonTappedCommand 
                property (which is a RelayCommand) in the ViewModel...
            -->
            
            <Button 
                Height="75"
                Width="300"
                HorizontalAlignment="Center" 
                Content="Add Default Item" 
                FontSize="28"
                Margin="10" 
                Command="{Binding ButtonTappedCommand}"/>
            
            <TextBlock Text="Tap colors to add them to the list..." FontSize="28" Margin="10" HorizontalAlignment="Center"/>
            
            <!-- 
                We want the rectangles to fire a command when tapped, but they don't have this feature. 
                So, we add an attached property from the TappedCommand class. This acts as an 'adapter'
                and sends the command to the ViewModel for us...
            -->
            
            <Rectangle 
                Height="75" 
                Width="300" 
                Fill="Red" 
                Margin="5"
                local:TappedCommand.Command="{Binding RectangleTappedCommand}"
                local:TappedCommand.CommandParameter="Red Item"/>
            
            <Rectangle 
                Height="75" 
                Width="300" 
                Fill="Green" 
                Margin="5"
                local:TappedCommand.Command="{Binding RectangleTappedCommand}"
                local:TappedCommand.CommandParameter="Green Item"/>                       
            
            <Rectangle 
                Height="75" 
                Width="300" 
                Fill="Blue" 
                Margin="5"
                local:TappedCommand.Command="{Binding RectangleTappedCommand}"
                local:TappedCommand.CommandParameter="Blue Item"/>                       

            <!-- 
                Use the SelectionCommand attached properties to add a command for the SelectionChanged event... 
            -->
            
            <ListView 
                Margin="10" 
                BorderBrush="Indigo" 
                BorderThickness="5"
                ItemsSource="{Binding Data}"
                ItemTemplate="{StaticResource ItemDataTemplate}" 
                local:SelectionCommand.Command="{Binding SelectionCommand}"
                local:SelectionCommand.CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}" />
        </StackPanel>
    </Grid>
</Page>

The following diagram shows the main components and how they communicate:

In addition to the View (which simply defines the UI elements in XAML) and ViewModel (see below) classes, the demo solution also contains the following classes:

RelayCommand

We've already meet the RelayCommand class in part one of this post. Essentially, this ICommand-derived class is a reusable container for encapsulating a command. It has a delegate for a method to call to execute the command, along with a CanExecute method which dictates if the command is enabled or not

TappedCommand

The TappedCommand class acts as an "command adapter" between a View element and the ViewModel. It defines attached RelayCommand properties which the UI element can use to fire custom commands. The TappedCommand class subscribes to a specific event (e.g. the Tapped event, in the case of the RGB rectangles in our demo) and calls the RelayCommand's delegate to execute the command when the event is raised. Here's the code:

using System.Windows.Input;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;

namespace MvvmDemo
{
    public static class TappedCommand
    {
        // *** Command attached property ***
        public static ICommand GetCommand(DependencyObject obj) 
        { 
            return (ICommand)obj.GetValue(CommandProperty); 
        }

        public static void SetCommand(DependencyObject obj, ICommand value) 
        { 
            obj.SetValue(CommandProperty, value); 
        }

        public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
            "Command",
            typeof(ICommand),
            typeof(TappedCommand),
            new PropertyMetadata(null, OnPropertyChanged));

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var rect = d as Rectangle;
            if (rect == null) return;

            // Hook into the relevant event...
            rect.Tapped += (sender, args) =>
            {
                var rectangle = (Rectangle)sender;

                // Get the attached property settings (set in XAML)...
                var command = rectangle.GetValue(TappedCommand.CommandProperty) as ICommand;
                var param = rectangle.GetValue(TappedCommand.CommandParameterProperty);

                // Call the ViewModel method that registered to handle this command
                // (this will have been set via data binding in XAML) ...
                if (command != null && command.CanExecute(param)) command.Execute(param);
            };
        }

        // *** CommandParameter attached property ***
        public static object GetCommandParameter(DependencyObject obj) 
        { 
            return (ICommand)obj.GetValue(CommandParameterProperty); 
        }

        public static void SetCommandParameter(DependencyObject obj, object value) 
        { 
            obj.SetValue(CommandParameterProperty, value); 
        }
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
            "CommandParameter",
            typeof(object),
            typeof(TappedCommand),
            new PropertyMetadata(null));
    }
}

SelectionCommand

The SelectionCommand class is another example of a command adapter. In this case, we're hooking into the SelectionChanged event to allow the ListView to fire selection changed commands to the ViewModel. The code is nearly identical to that of the TappedCommand class (the only differences are that it hooks into the SelectionChanged event and that the sender of the command is a ListView).

ViewModel

The code for the ViewModel is pretty straightforward. Notice how it defines RelayCommand properties for the various commands:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using MvvmDemo.Annotations;

namespace MvvmDemo
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private ObservableCollection<string> _data;
        private string _selectedDataItem;

        public ObservableCollection<string> Data
        {
            get { return _data; }
            set { _data = value; OnPropertyChanged(); }
        }

        public string SelectedDataItem
        {
            get { return _selectedDataItem ?? "Nothing selected"; }
            set { _selectedDataItem = value; OnPropertyChanged(); }
        }

        // Commands - View elements bind to these in XAML...
        public RelayCommand RectangleTappedCommand { get; set; }
        public RelayCommand ButtonTappedCommand { get; set; }
        public RelayCommand SelectionCommand { get; set; }

        public ViewModel()
        {
            Data = new ObservableCollection<string> { "Item 0", "Item 1", "Item 2" };
    
            // Wire-up the commands to their targets...
            RectangleTappedCommand = new RelayCommand(AddDataItem);
            ButtonTappedCommand = new RelayCommand(AddDefaultDataItem);
            SelectionCommand = new RelayCommand(SelectionChanged);
        }

        public void AddDataItem(object item)
        {
            var s = item as string;
            Data.Add(s);
        }

        public void AddDefaultDataItem(object item)
        {
            AddDataItem("Item " + Data.Count.ToString());
        }

        public void SelectionChanged(object selectedItem)
        {
            SelectedDataItem = selectedItem as string;
        }

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

References

These are the main references I used in relation to this post:

Conclusion

In this post I continued my introduction to fundamental MVVM concepts and we looked at how to use attached properties to add command-firing capabilities to UI elements. The main downside with this approach is that it relies on static methods. This means that you need to be careful when subscribing to events (don't accidentally subscribe multiple times to the same event). In part three of this post wrap-up things up by looking at some additional patterns related to MVVM.