Adventures in MVVM – Generalized Command Behavior Attachments

More Adventures in MVVM  Shout it kick it on DotNetKicks.com

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"/>

3 Responses to “Adventures in MVVM – Generalized Command Behavior Attachments”

  1. Eliyahu Weg says:

    Very good read useful information.

    Thank you very much,

    Quick question how would I control what properties/styles CanExecute of the command will change

  2. Martin Reich says:

    Brian:

    Thanks for the great post. Here is a snippet that automates the boilerplate code for the class that derives from ‘Attachment’. I have tested it out and it works just fine.

    Martin Reich

    Expansion

    slabehavior
    mreich
    Creates an SL attached behvior derived from ‘Attachment<>’

    slabehavior

    AttachedBehaviorName
    The behavior to attach to the object
    AttachedBehaviorName

    TargetObject
    The type of control that will use this attached behavior
    TargetObject

    AttachmentClassName
    AttachmentClass The name of the class that derives from Attachment<TargetObject> AttachmentClass Literal N/A True
    AttachmentClassName

    <![CDATA[ #region $AttachmentClassName$

    public class $AttachmentClassName$ : Attachment
    {
    private static readonly DependencyProperty BehaviorProperty = Behavior();
    public static readonly DependencyProperty CommandProperty = Command(BehaviorProperty);
    public static readonly DependencyProperty CommandParameterProperty = Parameter(BehaviorProperty);

    public static void SetCommand($TargetObject$ control, ICommand command) { control.SetValue(CommandProperty, command); }
    public static ICommand GetCommand($TargetObject$ control) { return control.GetValue(CommandProperty) as ICommand; }
    public static void SetCommandParameter($TargetObject$ control, object parameter) { control.SetValue(CommandParameterProperty, parameter); }
    public static object GetCommandParameter($TargetObject$ buttonBase) { return buttonBase.GetValue(CommandParameterProperty); }
    }

    #endregion]]>

  3. Farzad Jalali says:

    Brian,

    Thanks, that’s really great article.
    I’m interested about behaviors and I don’t know how to start.
    Any idea about a great website or a specific article with some hands-on please !

    Thanks,
    Farzad

Leave a Reply