Sunday, April 03, 2011

MVVM–Binding your VM to a Command

This post shows two ways in which you can connect UI elements like buttons in your View (V) to methods in your ViewModel (VM) with zero code in the view’s code-behind.

Here is what the View looks like:

image

The view displays the time and provides two buttons that allow you to update the time that’s being displayed on the page. (two buttons to demo the 2 ways to connect the view to the model)

First the ViewModel's code:

public class CommandingSampleViewModel : ViewModelBase
{
//exposes the UpdateTime method to the View
public DelegateCommand UpdateTimeCommand { get; private set; }

public CommandingSampleViewModel()
{
UpdateTimeCommand = new DelegateCommand(UpdateTime, CanExecuteUpdateTime);
if (IsDesignTime())
{
DateTimeString = "Hello World";
}
else
{
UpdateTime();
}
}

private void UpdateTime()
{
DateTimeString = DateTime.Now.ToString();
}

private bool CanExecuteUpdateTime()
{
return true;
}

public string _dateTimeString;
public string DateTimeString
{
get { return _dateTimeString; }
private set
{
if (_dateTimeString != value)
{
_dateTimeString = value;
RaisePropertyChanged(()=> DateTimeString);
}
}
}
}

The main thing to notice in the code above is the UpdateTimeCommand of type DelegateCommand. In the constructor, the UpdateTimeCommand is initialized and the method that is to be called when the UpdateTimeCommand is invoked from the view. The 2nd argument is a method that is provided for the view to determine if the command can be invoked. (In my sample it always returns true and hence you could use the other overloaded CTOR that doesn’t need the CanExecute method as a parameter).

The ViewModelBase used as the base class for the CommandingSampleViewModel above is defined below:

public class ViewModelBase : NotificationObject
{
protected bool IsDesignTime()
{
return DesignerProperties.IsInDesignTool;
}
}

Now the View’s code:

The first thing you need to do is to connect the View to its ViewModel. We first define the DataContext for the UserControl and then set the DataContext on the grid to the binding of the UserControl (done by simply setting the DataContext to {Binding}).

<UserControl.DataContext>
<vm:CommandingSampleViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding}">
</Grid>
</UserControl>

Next we connect up a TextBlock to the DateTimeString property of the ViewModel (CommandingSampleViewModel) defined above:

<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Time:" Margin="5"></TextBlock>
<TextBlock Text="{Binding Path=DateTimeString}" Margin="5"></TextBlock>
</StackPanel>
</Grid>

Finally we setup two buttons that are connected to the UpdateTimeCommand defined in the ViewModel.

First, the code for the simplest way to connect up the button to the UpdateTimeCommand and that is to use the Command property of the button. Here we set the Binding Path to the DelegateCommand “UpdateTimeCommand” defined in the ViewModel. That’s all there is to it.

<Button Content="Update Time using direct binding" Command="{Binding Path=UpdateTimeCommand}" Margin="5"></Button>

Another method also available to connect up a View’s button to a DelegateCommand is to use the “Interaction.Triggers”. This method has some extra glue code but one advantage is that it provides a lot more customizability and also allows you to connect the view to the view-model in Expression Blend. Here is what the code looks like:

<Button Content="Update Time using Interaction.Triggers" Margin="5">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path=UpdateTimeCommand}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>

Within the Interaction.Triggers node, an EventTrigger is defined that connects the Click event of the button to the ViewMode’s method via the InvokeCommandAction node’s Command parameter.

Here is the complete code for the View (including the namespace includes needed for the view)

<UserControl x:Class="Silverlight.DataAccessClient.Views.CommandingSampleView"
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"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:vm="clr-namespace:Silverlight.DataAccessClient.ViewModels"
mc:Ignorable="d"
d:DesignHeight="75" d:DesignWidth="400" Height="75">
<UserControl.DataContext>
<vm:CommandingSampleViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Time:" Margin="5"></TextBlock>
<TextBlock Text="{Binding Path=DateTimeString}" Margin="5"></TextBlock>
<StackPanel>
<Button Content="Update Time using direct binding" Command="{Binding Path=UpdateTimeCommand}" Margin="5"></Button>
<Button Content="Update Time using Interaction.Triggers" Margin="5">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path=UpdateTimeCommand}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>

There you have it, 2 simple ways in which to connect your view to your view-model’s method with ZERO code-behind in the View.

Here is what it looks like when the user-control is embedded in a Silverlight page:

image

Note: the DelegateCommand used is defined in Microsoft’s Prism framework (a great framework to use as a starting point for your MVVM implementation in Silverlight).

No comments: