More Adventures in MVVM

There are several examples on the web that describe the “Attached Behavior” pattern in Silverlight and WPF. This pattern works really well for binding commands in the ViewModel to controls in the View. The problem with this is that for every behavior, there is a LOT of boilerplate code that goes along with it. Because the DepencencyProperties need to be static, they cannot be easily abstracted into a common class.
If you want to attach a MouseEnterBehavior to a control, you need to create two or three static Dependency Properties in the MouseEnter class. They are MouseEnter.Command, MouseEnter.MouseEnterBehavior and optionally, MouseEnter.CommandParameter.
public class MouseEnter
{
private static readonly DependencyProperty BehaviorProperty =
DependencyProperty.RegisterAttached(
"MouseEnterBehavior",
typeof(MouseEnterBehavior),
typeof(Control),
null);
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(MouseEnter),
new PropertyMetadata(OnSetCommand));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(MouseEnter),
new PropertyMetadata(OnSetParameter))
And then the "Change Handlers"
private static void OnSetCommand(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var target = dependencyObject as Control;
if (target == null)
return;
GetOrCreateBehavior(target).Command = args.NewValue as ICommand;
}
private static void OnSetParameter(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var target = dependencyObject as Control;
if (target != null)
{
GetOrCreateBehavior(target).CommandParameter = args.NewValue;
}
}
protected static MouseEnterBehavior GetOrCreateBehavior(Control control)
{
var behavior = control.GetValue(BehaviorProperty) as MouseEnterBehavior;
if (behavior == null)
{
behavior = new MouseEnterBehavior(control);
control.SetValue(BehaviorProperty, behavior);
}
return behavior;
}
Since the Dependency Properties are static, Silverlight also requires you to in include Get* and Set* static methods:
public static void SetCommand(Control control, ICommand command) { control.SetValue(CommandProperty, command); }
public static ICommand GetCommand(Control control) { return control.GetValue(CommandProperty) as ICommand; }
public static void SetCommandParameter(Control control, object parameter) { control.SetValue(CommandParameterProperty, parameter); }
public static object GetCommandParameter(Control buttonBase) { return buttonBase.GetValue(CommandParameterProperty); }
This is a classic case of reuse via “Copy and Paste”. The problem is that there are several places in this code where you need to change three different types and many strings. If you don’t invoke the magic incantation properly, nothing works. It will compile but it won’t work (or you will get an obscure XAML parse error).
I cringe whenever I have to employ copy/paste reuse. In the cases where it is absolutely necessary (such as this), I believe the risk can be reduced proportionately to the complexity of the modification after you paste. This is why I came up with an Attachment base class to generalize all of this boilerplate code. The previous code can be reduced to:
public class MouseEnter : Attachment<Control, MouseEnterBehavior, MouseEnter>
{
private static readonly DependencyProperty BehaviorProperty = Behavior();
public static readonly DependencyProperty CommandProperty = Command(BehaviorProperty);
public static readonly DependencyProperty CommandParameterProperty = Parameter(BehaviorProperty);
public static void SetCommand(Control control, ICommand command) { control.SetValue(CommandProperty, command); }
public static ICommand GetCommand(Control control) { return control.GetValue(CommandProperty) as ICommand; }
public static void SetCommandParameter(Control control, object parameter) { control.SetValue(CommandParameterProperty, parameter); }
public static object GetCommandParameter(Control buttonBase) { return buttonBase.GetValue(CommandParameterProperty); }
}
The only modifications to this copied code exist on the first line.
What is the class name? MouseEnter (2 places)
What type of control can the behavior attach to? Control
What type of behavior do you want to attach? MouseEnterBehavior
In addition to the decreased configuration complexity, the actual code that needs to be copied goes from 58 lines of boilerplate code to 11 lines of boilerplate code. This is a big win, in my opinion.
In this code, I am using the CommandBehaviorBase class from the Prism framework. It is part of the generic constraints. If you use something else for your behaviors, replace it as you see fit. Your own base class for command behaviors would slip in nicely, I am sure.
Here is the Attachment base class:
public class Attachment<TargetT, BehaviorT, AttachmentT>
where TargetT : Control
where BehaviorT : CommandBehaviorBase<TargetT>
{
public static DependencyProperty Behavior()
{
return DependencyProperty.RegisterAttached(
typeof(BehaviorT).Name,
typeof(BehaviorT),
typeof(TargetT),
null);
}
public static DependencyProperty Command(DependencyProperty behaviorProperty)
{
return DependencyProperty.RegisterAttached(
"Command",
typeof (ICommand),
typeof(AttachmentT),
new PropertyMetadata((target, args) => OnSetCommandCallback(target, args, behaviorProperty)));
}
public static DependencyProperty Parameter(DependencyProperty behaviorProperty)
{
return DependencyProperty.RegisterAttached(
"CommandParameter",
typeof (object),
typeof (AttachmentT),
new PropertyMetadata((target, args) => OnSetParameterCallback(target, args, behaviorProperty)));
}
protected static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e, DependencyProperty behaviorProperty)
{
var target = dependencyObject as TargetT;
if (target == null)
return;
GetOrCreateBehavior(target, behaviorProperty).Command = e.NewValue as ICommand;
}
protected static void OnSetParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e, DependencyProperty behaviorProperty)
{
var target = dependencyObject as TargetT;
if (target != null)
{
GetOrCreateBehavior(target, behaviorProperty).CommandParameter = e.NewValue;
}
}
protected static BehaviorT GetOrCreateBehavior(Control control, DependencyProperty behaviorProperty)
{
var behavior = control.GetValue(behaviorProperty) as BehaviorT;
if (behavior == null)
{
behavior = Activator.CreateInstance(typeof(BehaviorT), control) as BehaviorT;
control.SetValue(behaviorProperty, behavior);
}
return behavior;
}
}
And finally, just to complete the example, here is what the MouseEnterBehavior looks like:
public class MouseEnterBehavior : CommandBehaviorBase<Control>
{
public MouseEnterBehavior(Control selectableObject)
: base(selectableObject)
{
selectableObject.MouseEnter += (sender, args) => ExecuteCommand();
}
}
And to use it in your XAML, it would look like this:
<Button Behaviors:MouseEnter.Command="{Binding MouseEnter}" Behaviors:MouseEnter.CommandParameter="Optional Paremeter"/>