Adventures in MVVM
EDIT:
One of the things that I enjoy most about working with MVVM in Silverlight is how new it is. When I say “new”, I mean that Silverlight doesn’t support the pattern very well out of the box, so the development community needs to step up and solve these problems. Some solutions are better than others. In this article, I solved a problem the best way I knew how. I urge you to read Ward Bell’s comments to this article, and my responses. After reflecting on it more, I concede that this is not the best way to implement a lightweight bindable command in SIlverlight.
Instead, I should have just read the Prism 2 source code to see how they implemented commands via attached behaviors. I would have learned that that I learned attached behaviors wrong in the first place. In Julian Dominguez’s blog post on the topic, he walks you through the thought process for attaching commands via behaviors. Although this is not the final code that made it into Prism, it is very close. I recommend reading it.
That being said, I will keep my original text in place. I think that the thought process for how I got there is very useful for learning… at least I find it useful. Then, be sure to read the comments, visit Julian’s blog and browse the Prism (CAL) source code.
One of the most important aspects of implementing the MVVM pattern in WPF and SIlverlight is the ability for the UI layer to bind directly to commands in the ViewModel. The only problem with this: commands were never implemented in Silverlight. Even though I (and many others) have ranted about this, our voice has not been heard. Even with the release of the Silverlight 3 beta, it seems as if we are still pining for commanding in Silverlight.
Many libraries have implemented commands in Silverlight, usually with some sort of static lookup table, mapping buttons to commands. They include Prism, Caliburn, SilverlightFX and the MVVM toolkit. It can feel like overkill to bring in these libraries just to get commanding. There are plenty of good reasons to use these libraries – don’t get me wrong – but if you are just looking for bindable commands, there is an easier way.
This article will walk you through the process of creating a button with command properties. This technique can be translated easily to any other control in order to achieve bindable commanding in Silverlight.
ICommand
The ICommand interface was the only thing that was included from the WPF commanding infrastructure within Silverlight. The interface is extremely simple:
interface ICommand
{
void Execute(object parameter);
bool CanExecute(object parameter);
event EventHandler CanExecuteChanged;
}
The requirements of any control that deals with ICommand are:
- Call Execute() when a trigger is hit
- Only call Execute() if CanExecute() returns true
- Allow a bindable parameter to be passed into Execute() and CanExecute()
- Disable the control when CanExecute() is false
- Refresh the enable/disable state of the control when the CanExecuteChanged event is raised
Implementing CommandButton
Lets start with requirements 1 and 2:
public class CommandButton : Button
{
public CommandButton()
{
Click += (sender, e) =>
{
if (Command != null && Command.CanExecute(null))
Command.Execute(null);
};
}
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command",
typeof(ICommand), typeof(CommandButton),
new PropertyMetadata(null));
public ICommand Command
{
get { return GetValue(CommandProperty) as ICommand; }
set { SetValue(CommandProperty, value); }
}
}
With this, you can bind a command in the ViewModel to the view:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
MyCommand = new DelegateCommand<object>(DoSomething);
}
private void DoSomething(object obj)
{
// Do what you want
}
public ICommand MyCommand { get; private set; }
// The rest of your ViewModel
}
This XAML creates a CommandButton in place of a Button:
<local:CommandButton Content="Click Me" Command="{Binding MyCommand}" />
Adding Parameters
Implementing feature 3 is trivial. Add the CommandParameter property and pass it in to Execute() and CanExecute()
public class CommandButton : Button
{
public CommandButton()
{
Click += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
// Everything else from initial Implementation
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter",
typeof(object), typeof(CommandButton),
new PropertyMetadata(null));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
With that, you can add parameters to the XAML:
<local:CommandButton Content="Click Me" Command="{Binding MyCommand}" CommandParameter="MyParameter" />
Hooking IsEnabled to CanExecute()
Things get a bit more complicated when implementing requirements 4 and 5, but it is still pretty straight-forward. I start by registering an event handler for when the Command property changes (CommandChanged). This event handler hooks the CanExecuteChanged event and handles the event by setting the IsEnabled flag to the value of CanExecute(). It then proceeds to initialize the value of IsEnabled since we know the answer at this time.
The final class:
public class CommandButton : Button
{
public CommandButton()
{
Click += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command",
typeof(ICommand), typeof(CommandButton),
new PropertyMetadata(null, CommandChanged));
private static void CommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
{
var button = source as CommandButton;
if (button == null) return;
button.RegisterCommand(args.OldValue as ICommand, args.NewValue as ICommand);
}
private void RegisterCommand(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
oldCommand.CanExecuteChanged -= HandleCanExecuteChanged;
if (newCommand != null)
newCommand.CanExecuteChanged += HandleCanExecuteChanged;
HandleCanExecuteChanged(newCommand, EventArgs.Empty);
}
private void HandleCanExecuteChanged(object sender, EventArgs args)
{
if (Command != null)
IsEnabled = Command.CanExecute(CommandParameter);
}
public ICommand Command
{
get { return GetValue(CommandProperty) as ICommand; }
set { SetValue(CommandProperty, value); }
}
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter",
typeof(object), typeof(CommandButton),
new PropertyMetadata(null));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
Summary
This method for hooking commands to buttons has one drawback in my opinion: It requires you to put a CommandButton in your XAML instead of a vanilla Button. This, of course, means that other controls that inherit from Button such as Checkbox and RadioButton do not get this behavior (you have to implement this pattern for them). Still, this approach makes it very easy to add commands to any control you wish; even in WPF. In a future post, I will discuss a similar approach for binding a command to a ComboBox (or ListBox) selection changed event.