DM-V-VM part 7: Encapsulating commands

In part 5, I talked about commands and how they are used for behavior. Now, I want to talk about a better way to encapsulate them. First, I'll create a CommandModel class that encapsulates the RoutedCommand and the enabled/execute code. This is all pretty straightforward:

    public abstract class CommandModel

    {

        public CommandModel()

        {

            _routedCommand = new RoutedCommand();

        }

 

        /// <summary>

        /// Routed command associated with the model.

        /// </summary>

        public RoutedCommand Command

        {

            get { return _routedCommand; }

        }

 

        /// <summary>

        /// Determines if a command is enabled. Override to provide custom behavior. Do not call the

        /// base version when overriding.

        /// </summary>

        public virtual void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)

        {

            e.CanExecute = true;

            e.Handled = true;

        }

 

        /// <summary>

        /// Function to execute the command.

        /// </summary>

        public abstract void OnExecute(object sender, ExecutedRoutedEventArgs e);

 

        private RoutedCommand _routedCommand;

    }

Subclasses could add more properties to the RoutedCommand if need be. You could also imagine adding more properties to CommandModel for things like an icon to display in the UI. For example, in Max, our toolbars are ItemsControls bound to a list of CommandModels with a data template for each command model that shows the icon and name of the command.

Now, we can wrap our simple command from part 5 into a class:

    class MyCommand : CommandModel

    {

        public override void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)

        {

            e.CanExecute = !string.IsNullOrEmpty(e.Parameter as string);

            e.Handled = true;

        }

 

        public override void OnExecute(object sender, ExecutedRoutedEventArgs e)

        {

            string text = e.Parameter as string;

 

            // Do something with text

        }

    }

We can now remove all of the command related code from the window class. We're still stuck setting up the binding in the UI class. But, we can use the attached property trick to get rid of this, similar to how we did the activation in part 6. We'll create a property that you can attach to a UI element like

local:CreateCommandBinding.Command="{Binding MyCommand}"

To create a command binding for a given command model. First we just need the standard attached property definition, set up to call OnCommandInvalidated when the property changes:

    public static class CreateCommandBinding

    {

        public static readonly DependencyProperty CommandProperty

           = DependencyProperty.RegisterAttached("Command", typeof(CommandModel), typeof(CreateCommandBinding),

                new PropertyMetadata(new PropertyChangedCallback(OnCommandInvalidated)));

 

        public static CommandModel GetCommand(DependencyObject sender)

        {

            return (CommandModel)sender.GetValue(CommandProperty);

        }

 

        public static void SetCommand(DependencyObject sender, CommandModel command)

        {

            sender.SetValue(CommandProperty, command);

        }

 And, OnCommandInvalidated will set up the binding when the command is changed.

        private static void OnCommandInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)

        {

            // Clear the exisiting bindings on the element we are attached to.

            UIElement element = (UIElement)dependencyObject;

            element.CommandBindings.Clear();

 

            // If we're given a command model, set up a binding

            CommandModel commandModel = e.NewValue as CommandModel;

            if (commandModel != null)

            {

                element.CommandBindings.Add(new CommandBinding(commandModel.Command, commandModel.OnExecute, commandModel.OnQueryEnabled));

            }

 

            // Suggest to WPF to refresh commands

            CommandManager.InvalidateRequerySuggested();

        }

 Note: I was a little lazy implementing the clearing code. You can't have multiple bindings on element you put this on. A more general implementation would be to clear the specific binding for e.OldValue.

 Putting it together, the Xaml for the window now looks like:

<Window x:Class="CommandDemo.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:CommandDemo"

    Title="CommandDemo" Height="300" Width="300"

    DataContext="{Binding RelativeSource={RelativeSource Self}}"

    >

    <StackPanel>

      <TextBox Name="_textBox"/>

      <Button Name="_button"

              local:CreateCommandBinding.Command="{Binding MyCommand}"

              Command="{Binding MyCommand.Command}"

              CommandParameter="{Binding Text, ElementName=_textBox}">

        Do something

      </Button>

    </StackPanel>

</Window>

 The only difference is the CreateCommandBinding property. And, the code behind looks like:

    public partial class Window1 : Window

    {

        public Window1()

        {

            InitializeComponent();

        }

 

        public CommandModel MyCommand

        {

            get { return _myCommand; }

        }

 

        CommandModel _myCommand = new MyCommand();

    }

 So, we've extracted almost all of the behavior out of the Window's code behind. The command code is nicely encapsulated into a class with no coupling to the UI, so it can be easily reused and unit tested.

The way things are now, the window is setting itself as its DataContext and exposing the behavior of the windows through bindings via that DataContext. As a preview of what's coming next, we'll move that behavior out to a separate class (which I'm calling a ViewModel) and set the DataContext of the window to the view model instead of itself. We'll be left with no code behind (except InitializeComponent) and clean separation between the style and the behavior.