Introduction
Implementing MVVM strictly in Silverlight has many pain points. Some of these are:
- Handling Events in Silverlight controls such as
Loaded
, MouseRightButtonDown
, KeyDown
, etc.
- Handling Events and Properties inside a ChildWindow or
Popup
.
- Closing a ChildWindow.
This project was created in order to make use of the following Microsoft Blend 4.0 Behaviors to ease the many pain points of implementing MVVM using Silverlight.
- InvokeCommandAction. The
InvokeCommandAction
action specifies the target object that contains the command that you want to invoke. (source: Microsoft)
- CallMethodAction. You can use the
CallMethodAction
action to call a method that is defined for a specified object. The method being called must be a public
method that takes no arguments and does not return a value or a public
method whose signature matches that of an event handler. (source: Microsoft)
- ChangePropertyAction. You can use the
ChangePropertyAction
behavior to easily either change or increment the property of an object and then, optionally, define a transition. By default, the transition is instantaneous.
Setting up the Forms
- Create a New Silverlight Application + Website in Microsoft Blend named
MVVMWithBlend
.
- Add a new ChildWindow named childWindow1.xaml. Click on the Projects tab and right-click on the project file named
MVVMWithBlend
. Click on Add New Item... and select ChildWindow
. Type ChildWindow1.xaml on the name and click Ok. This will create a ChildWindow
control.
- Go back to MainPage.xaml and add the following controls as shown below:
NOTE: You can paste the following code in Bold below for convenience:
<Grid x:Name="LayoutRoot" Background="White">
<Grid Height="30" Margin="128,139,203,0" VerticalAlignment="Top">
<Rectangle Fill="#FFF4F4F5" Stroke="Black"/>
<TextBlock HorizontalAlignment="Left" Margin="98,4,0,3"
x:Name="textBlock1" Text="Right Click on Me" />
</Grid>
<TextBox Height="36" Margin="128,0,203,193" TextWrapping="Wrap"
Text="TextBox" VerticalAlignment="Bottom"/>
<Ellipse Fill="#FFF4F4F5" Height="31" Margin="149,199,278,0"
Stroke="Black" VerticalAlignment="Top"/>
<Button Content="Open Child Window" Height="23"
HorizontalAlignment="Left" Margin="128,92,0,0" x:Name="button1"
VerticalAlignment="Top" Width="147" />
</Grid>
Setting up the Behaviors
- Click on Assets > Behaviors and add the InvokeCommandAction to the controls as shown below:
- Set the following
InvokeCommandAction
Properties on Grid
:
- Command Property = "
TextInputUpdateCommand
"
- EventName = "
MouseRightButtonDown
"
- Set the following
InvokeCommandAction
Properties on TextBox
:
- Command Property = "
TextInputUpdateCommand
"
- EventName = "
KeyDown
"
- Set the following
InvokeCommandAction
properties on Ellipse
:
- Command Property = "
LoadedCommand
"
- EventName = "
Loaded
"
NOTE: You can paste the following code in Bold below for convenience:
<Grid x:Name="LayoutRoot" Background="White">
<Grid Height="30" Margin="128,139,203,0" VerticalAlignment="Top">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonDown">
<i:InvokeCommandAction Command="{Binding TextInputUpdateCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Rectangle Fill="#FFF4F4F5" Stroke="Black"/>
<TextBlock HorizontalAlignment="Left" Margin="98,4,0,3"
x:Name="textBlock1" Text="Right Click on Me" />
</Grid>
<TextBox Height="36" Margin="128,0,203,193"
TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Bottom">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<i:InvokeCommandAction Command="{Binding TextInputUpdateCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Ellipse Fill="#FFF4F4F5" Height="31" Margin="149,199,278,0"
Stroke="Black" VerticalAlignment="Top">
<i:Interaction.Triggers>
<i:EventTrigger>
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Ellipse>
<Button Content="Open Child Window" Command="{Binding PopupVM}"
Height="23" HorizontalAlignment="Left" Margin="128,92,0,0"
x:Name="button1" VerticalAlignment="Top" Width="147" />
</Grid>
- Open the ChildWindow1.xaml and replace the following code in Bold with the one shown below:
<controls:ChildWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls"
xmlns:i=http://schemas.microsoft.com/expression/2010/interactivity
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Name="childWindow" x:Class="MVVMWithBlend.ChildWindow1"
Width="400" Height="296"
Title="ChildWindow1">
<Grid x:Name="LayoutRoot" Margin="2,0,2,-35">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" MinHeight="31" />
</Grid.RowDefinitions>
<Button x:Name="CancelButton" Content="Cancel" Width="75"
Height="23" HorizontalAlignment="Right" VerticalAlignment="Bottom"
d:LayoutOverrides="Height" Margin="0,0,0,4" />
<Button x:Name="OKButton" Content="OK" Width="75" Height="23"
HorizontalAlignment="Left" Margin="224,0,0,4" VerticalAlignment="Bottom"
d:LayoutOverrides="Height"/>
</Grid>
</controls:ChildWindow>
- Remove the folllowing code behind in ChildWindow1.xaml.cs:
private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
}
- Click on Assets > Behaviors and add the CallMethodAction and ChangePropertyAction to the OK and Cancel buttons as shown below:
- Set the following
CallMethodAction
Properties in CancelButton
:
EventName
= "Click
"
TargetObject
= "System.Windows.Controls.ChildWindow
" (Use the Artboard element picker and click on the Child Window)
MethodName
= "Close
"
- Set the following
ChangePropertyAction
properties in CancelButton
:
EventName
= "Click
"
TargetObject
= "System.Windows.Controls.ChildWindow
" (Use the Artboard
element picker and click on the Child Window)
PropertyName
= "DialogResult
"
Value
= checkmark
- Set the following
CallMethodAction
properties in OKButton
:
EventName
= "Click
"
TargetObject
= "System.Windows.Controls.ChildWindow
" (Use the Artboard
element picker and click on the Child Window)
MethodName
= "Close
"
- Set the following
ChangePropertyAction
properties in OKButton
:
EventName
= "Click
"
TargetObject
= "System.Windows.Controls.ChildWindow
" (Use the Artboard
element picker and click on the Child Window)
PropertyName
= "DialogResult
"
Value
= checkmark
NOTE: You can also copy and paste the following code for convenience:
<controls:ChildWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Name="childWindow" x:Class="MVVMWithBlend.ChildWindow1"
Width="400" Height="300"
Title="ChildWindow1">
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button x:Name="CancelButton" Command="{Binding CancelCommand}"
Content="Cancel" Width="75" Height="23" HorizontalAlignment="Right"
Margin="0,12,0,0" Grid.Row="1" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction MethodName="Close"
TargetObject="{Binding ElementName=childWindow}" />
<ei:ChangePropertyAction TargetObject="{Binding ElementName=childWindow}"
PropertyName="DialogResult" Value="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button x:Name="OKButton" Command="{Binding OKCommand}"
Content="OK" Width="75" Height="23" HorizontalAlignment="Left"
Margin="224,12,0,0" Grid.Row="1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction MethodName="Close"
TargetObject="{Binding ElementName=childWindow}" />
<ei:ChangePropertyAction TargetObject="{Binding ElementName=childWindow}"
PropertyName="DialogResult" Value="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</controls:ChildWindow>
Setting up the ViewModel
Add a new class named ViewModel.cs. Click on the Projects tab and right-click on the project file named MVVMWithBlend
. Click on Add New Item... and select Class
. Type ViewModel
on the name and click Ok.
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace MVVMWithBlend
{
public class ViewModel
{
#region ICommands
public ICommand TextInputUpdateCommand
{
get
{
return new InvokeTextUpdateCommand();
}
}
public ICommand LoadedCommand
{
get
{
return new InvokeLoadedCommand();
}
}
public ICommand PopupVM
{
get
{
return new InvokeChildCommand();
}
}
#endregion
#region Classes
public class InvokeTextUpdateCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null)
{
CanExecuteChanged.Invoke(parameter, new EventArgs());
}
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Text is Updated");
}
}
public class InvokeLoadedCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null)
{
CanExecuteChanged.Invoke(parameter, new EventArgs());
}
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Loaded Event is Triggered");
}
}
public class InvokeChildCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null)
{
CanExecuteChanged.Invoke(parameter, new EventArgs());
}
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
ChildWindow1 child = new ChildWindow1();
ViewModelPopup pop = new ViewModelPopup();
child.DataContext = pop;
child.Show();
}
}
public class ViewModelPopup
{
public ICommand OKCommand
{
get
{
DialogResult = true;
return new InvokeOkCommand();
}
}
public ICommand CancelCommand
{
get
{
DialogResult = false;
return new InvokeCancelCommand();
}
}
public class InvokeOkCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null)
{
CanExecuteChanged.Invoke(parameter, new EventArgs());
}
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Ok is Clicked");
}
}
public class InvokeCancelCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null)
{
CanExecuteChanged.Invoke(parameter, new EventArgs());
}
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Cancel is Clicked");
}
}
}
#endregion
}
}
History
- 06/01/2011: Initial draft
- 06/02/2011: Adjusted lines and corrected copy and paste error