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=http://schemas.microsoft.com/winfx/2006/xaml/presentation


    xmlns:x=http://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.

Comments (8)

  1. If you’re doing WPF development, you really need to check out Dan Crevier ‘s series on DataModel-View-ViewModel.

  2. I thought I should add a post with the full list of posts in the D-V-VM pattern. They are: DataModel-View-ViewModel

  3. makutaku says:

    Unfortunately this technique doesn’t allow me to set multiple bindings in XAML because I can’t do something such as:

    <Grid

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

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

    >

    Any idea how could set multiple bindings for the command models in XAML?

  4. You’ll need to modify CreateCommandBinding.OnCommandInvalidated to not clear the binding. To really do it right, you’ll need to remove the old command binding when the command changes.

  5. makutaku says:

    If I remove the old command binding when the command changes then, using my example above, MyCommand2 binding will remove MyCommand1 binding.  

    If I want to have multiple simultaneous bindings then I guess I just have to not clear the binding. I’ll try that.

  6. makutaku says:

    Unfortunately I can’t have two simultaneous bindings using XAML:

    <Grid

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

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

    >

    because I got an error saying:

    Error 54 ”p:CommandBindingCreator.Command’ is a duplicate attribute name. Line 17, position 3.’ XML is not valid.

    The only way I found to do that was to create another attached property called Commands which takes a collection of CommandModels.

  7. Sorry, I wasn’t clear on the clearing. I meant you should remove e.OldValue from the binding in the property changed handler.

    To handle multiple bindings, you could also allow CreateCommandBinding take a IEnumerable<CommandModel> and expose a list from the model.

  8. If you are trying to write an application in WPF that separates the UI from the underlying business logic