Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

February 14, 2011

Silverlight MVVM Commanding II

Filed under: C#,Silverlight,VB.NET @ 12:47 am

This prior post introduced commanding in Silverlight for use with MVVM. However, the built-in Silverlight commanding only works for controls that inherit from ButtonBase, such as Button and HyperlinkButton. This post provides information on implementing commanding for other Silverlight controls.

For example, the application displays a DataGrid. A click on a grid row should display information about the row that was clicked. But DataGrid does not inherit from ButtonBase and therefore does not support the built-in commanding.

The key to extending Silverlight commanding is Expression Blend’s InvokeCommandAction, which requires the Expression Blend SDK for Silverlight 4. If you don’t have this SDK, you can download it here.

In your Silverlight project, reference the System.Windows.Interactivity.dll. On my system, this file was in C:\Program Files (x86)\Microsoft SDKs\Expression\Blend\Silverlight\v4.0\Libraries. Set it to Copy Local so that it is deployed with your application.

Using the InvokeCommandAction commanding feature in a Silverlight MVVM application requires three basic steps:

1) Create a command class that implements the ICommand interface.

2) Create a property of type ICommand in the View/Model class.

3) Bind the Command property of any control to the property created in 2 above.

If you read the prior post on Simple MVVM Commanding, you will notice that these are the same three steps. The only difference is the syntax for step #3.

Creating a Command Class

The Command class used for InvokeCommandAction is the same class you use for the built-in command feature. The code for this class was provided in this prior post and is not repeated here.

Creating a Property in the View/Model

Add three things to the View/Model for each command:

1) Command property.

2) Code to set the command property.

3) Method defined for the command property delegate.

The code is shown below with the statements for 1, 2, and 3 above shown in red.

In C#:

using System;
using System.Collections.ObjectModel;
using System.ServiceModel.DomainServices.Client;
using System.Windows.Input;
using InStepSM.Library;
using InStepSM.SL.Web;
using System.Windows;
using System.Windows.Controls.Primitives;

namespace InStepSM.SL.ViewModels
{
    public class StudentsViewModel : ViewModelBase
    {
        private ICommand _SelectedCommand;
        public ICommand SelectedCommand
        {
            get { return _SelectedCommand; }
        }
       
        private StudentContext StudentContextInstance;

        private ObservableCollection<Student> _StudentsList;
        public ObservableCollection<Student> StudentsList
        {
            get
            {
                return _StudentsList;
            }
            set
            {
                if (_StudentsList != value)
                {
                    _StudentsList = value;
                    OnPropertyChanged("StudentsList");
                }
            }
        }

        public StudentsViewModel()
        {
            LoadData();
            DefineCommands();
        }

        private void DefineCommands()
        {
            _SelectedCommand = new DelegateCommand<Student>
                            (OnSelectedCommand);
        }

        /// <summary>
        /// Loads the data for the application.
        /// </summary>
        private void LoadData()
        {
            if (IsInDesignModeStatic)
            {
                LoadDesignData();
            }
            else
            {
                // Load the student data asynchronously
                StudentContextInstance = new StudentContext();
                var loadop =
                  StudentContextInstance.Load(StudentContextInstance.
                                      GetStudentsQuery(),
                                      OnStudentsLoaded, null);
            }
        }

        /// <summary>
        /// Loads temporary data for use in the designer.
        /// </summary>
        /// <remarks></remarks>
        private void LoadDesignData()
        {
            //Design mode data
            ObservableCollection<Student> temp =
                   new ObservableCollection<Student>();
            temp.Add(new Student
            {
                LastName = "Baggins",
                FirstName = "Bilbo",
                Age = 111,
                Email = "testemail@live.com",
                RegistrationDate = DateTime.Now
            });
            temp.Add(new Student
            {
                LastName = "Baggins",
                FirstName = "Frodo",
                Age = 32,
                Email = "testemail@yahoo.com",
                RegistrationDate = DateTime.Now
            });
            StudentsList = temp;
        }

        private void OnStudentsLoaded(LoadOperation lo)
        {
            StudentsList = new ObservableCollection<Student>(StudentContextInstance.Students);
        }

        private void OnSelectedCommand(Student s)
        {
            string prompt = string.Format(
                "Selected student is: {0}, {1}",
                s.LastName, s.FirstName);
            MessageBox.Show(prompt, "Students", MessageBoxButton.OK);
        }
    }
}

In VB:

Imports System.Collections.ObjectModel
Imports System.ServiceModel.DomainServices.Client
Imports InStepSM.SL.Web

Namespace ViewModels
    Public Class StudentsViewModel
        Inherits ViewModelBase

        Private _SelectedCommand As ICommand
        Public ReadOnly Property SelectedCommand() As ICommand
            Get
                Return _SelectedCommand
            End Get
        End Property

        Private _StudentsList As ObservableCollection(Of Student)
        Public Property StudentsList() As
                                 ObservableCollection(Of Student)
            Get
                Return _StudentsList
            End Get
            Set(ByVal value As ObservableCollection(Of Student))
                If _StudentsList IsNot value Then
                    _StudentsList = value
                    OnPropertyChanged("StudentsList")
                End If
            End Set
        End Property

        Private StudentContextInstance As StudentContext

        Public Sub New()
            LoadData()
            DefineCommands()
        End Sub

        Private Sub DefineCommands()
            _SelectedCommand = New DelegateCommand(Of Student)(
                            AddressOf OnSelectedCommand)
        End Sub

        ”’ <summary>
        ”’ Loads the data for the application.
        ”’ </summary>
        Private Sub LoadData()
            If IsInDesignModeStatic Then
                LoadDesignData()
            Else
                ‘ Load the student data asynchronously
                StudentContextInstance = New StudentContext
                Dim loadop =
                  StudentContextInstance.Load(StudentContextInstance.
                               GetStudentsQuery(),
                               AddressOf OnStudentsLoaded, Nothing)
            End If
        End Sub

        ”’ <summary>
        ”’ Loads temporary data for use in the designer.
        ”’ </summary>
        ”’ <remarks></remarks>
        Private Sub LoadDesignData()
            ‘ Design mode data
            Dim temp As New ObservableCollection(Of Student)
            temp.Add(New Student With {.LastName = "Baggins",
                                       .FirstName = "Bilbo",
                                       .Age = 111,
                                       .Email = "testemail@live.com",
                                       .RegistrationDate = Now()})
            temp.Add(New Student With {.LastName = "Baggins",
                                       .FirstName = "Frodo",
                                       .Age = 32,
                                       .Email = "testemail@yahoo.com",
                                       .RegistrationDate = Now()})
            StudentsList = temp

        End Sub

        Private Sub OnStudentsLoaded(ByVal lo As LoadOperation)
            StudentsList = New ObservableCollection(Of Student)
                              (StudentContextInstance.Students)
        End Sub

        Private Sub OnSelectedCommand(ByVal s As Student)
            Dim prompt = String.Format("Selected student is: {0}, {1}",
                s.LastName, s.FirstName)
            MessageBox.Show(prompt, "Students", MessageBoxButton.OK)
        End Sub

    End Class
End Namespace

The SelectedCommand is the property that implements ICommand. The UI can be bound to this property.

The OnSelectedCommand is the method to be executed when the command occurs.

The SelectedCommand is set to an instance of the DelegateCommand, passing in OnSelectedCommand as the delegate.

Note that in this example, the parameter is used in the message box to display the name of the selected student.

Binding to the Command

This last step is where the InvokeCommandAction technique is different from the built-in commanding used for ButtonBase controls.

Insert the following lines of code within the control that generates the event associated with the command.

<i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseLeftButtonUp">
        <i:InvokeCommandAction
            Command="{Binding SelectedCommand}"
            CommandParameter="{Binding SelectedItem,
                        ElementName=studentDataGrid}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

NOTE: The Interactivity namespace is defined as follows:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=
                       System.Windows.Interactivity"

In this example, the Interaction code is added to the DataGrid. The EventName property defines the event that triggers the command. In this case, the MouseLeftButtonUp event. When the user clicks on a row in the DataGrid, the SelectedCommand is executed.

NOTE: To find the set of valid EventName values for any control, consult the msdn documentation. For example, for the DataGrid, the valid set of EventName values are listed under "Events" in this msdn link.

The Command binds to the defined command property, in this case the SelectedCommand. The optional CommandParameter binds to any information passed to the command. In this example, the DataGrid’s selected item is passed to the SelectCommand, passing in the selected student.

The full xaml is shown below.

<navigation:Page x:Class="InStepSM.SL.Views.Students"
       
xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:d="
http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=
            System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="Students Page" 
xmlns:sdk=
           
http://schemas.microsoft.com/winfx/2006/xaml/
            presentation/sdk

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=
            System.Windows.Interactivity"
xmlns:vms="clr-namespace:InStepSM.SL.ViewModels">

    <!– Reference the View/Model –>
    <navigation:Page.DataContext>
        <vms:StudentsViewModel />
    </navigation:Page.DataContext>

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBlock x:Name="HeaderText"
                   Grid.Row="0"
                   Grid.Column="0" Grid.ColumnSpan="2"
                    Style="{StaticResource HeaderTextStyle}"
                    Text="Students" />
             
        <sdk:DataGrid AutoGenerateColumns="False"
                Grid.Row="1" Grid.Column="1"
                Height="200"
                HorizontalAlignment="Stretch"
                ItemsSource="{Binding StudentsList}"
                Name="studentDataGrid"
                RowDetailsVisibilityMode="VisibleWhenSelected"
                VerticalAlignment="Top"
                Width="457">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonUp">
                    <i:InvokeCommandAction
                        Command="{Binding SelectedCommand}"
                        CommandParameter="{Binding SelectedItem,
                                   ElementName=studentDataGrid}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>

            <sdk:DataGrid.Columns>
            <sdk:DataGridTextColumn x:Name="firstNameColumn"
                                    Binding="{Binding Path=FirstName}"
                                    Header="First Name"
                                    Width="SizeToHeader" />
            <sdk:DataGridTextColumn x:Name="lastNameColumn"
                                    Binding="{Binding Path=LastName}"
                                    Header="Last Name"
                                    Width="SizeToHeader" />
            <sdk:DataGridTextColumn x:Name="ageColumn"
                                    Binding="{Binding Path=Age}"
                                    Header="Age"
                                    Width="SizeToHeader" />
            <sdk:DataGridTextColumn x:Name="emailColumn"
                                    Binding="{Binding Path=Email}"
                                    Header="Email"
                                    Width="120" />
            <sdk:DataGridTemplateColumn x:Name="registrationDateColumn"
                                    Header="Registration Date" 
                                    Width="SizeToHeader">
            <sdk:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <sdk:DatePicker SelectedDate="{Binding                    Path=RegistrationDate, Mode=TwoWay,
                   NotifyOnValidationError=true, 
                   ValidatesOnExceptions=true,
                   TargetNullValue=”}" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellEditingTemplate>
            <sdk:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding
                      Path=RegistrationDate, StringFormat=\{0:d\}}" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellTemplate>
        </sdk:DataGridTemplateColumn>
        </sdk:DataGrid.Columns>
        </sdk:DataGrid>

        <Grid Grid.Row="2"
              Grid.Column="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

        </Grid>
    </Grid>
</navigation:Page>

Running the application and clicking on a row results in the following:

image

Use this technique to add commanding to any Silverlight control.

Enjoy!

1 Comment

  1.   Nk54 — June 21, 2011 @ 10:23 am    Reply

    OMG !!! Thanks for sharing ! I’ve seen dozen post on how to do it.

    Your solution doesn’t work with mouseleftdown

    but who cares ? i prefer mouseleftbutonUp than down if i got no changes or no dependecy to add !

RSS feed for comments on this post. TrackBack URI

Leave a comment

© 2014 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs