다음을 통해 공유


DM-V-VM part 5: Commands

In the previous posts, I've covered data models and how to make data easily consumable by Avalon. Now, I want to start getting into the view model side of things because I don't think it's clear what I mean by them and I'm working up to an example.

I want to start by talking about handling behavior in the UI. Let's take a simple example. Say you have some UI that has a TextBox and a Button that does some operation on that text. The standard way you might do something like this with Xaml and code behind is something like:

<Window x:Class="CommandDemo.Window1"

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

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

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

    >

    <StackPanel>

      <TextBox Name="_textBox"/>

      <Button Name="_button" Click="OnButtonClick">Do something</Button>

    </StackPanel>

</Window>

and the code behind:

namespace CommandDemo

{

    public partial class Window1 : Window

    {

 

        public Window1()

        {

            InitializeComponent();

        }

 

        private void OnButtonClick(object sender, RoutedEventArgs e)

        {

            string text = _textBox.Text;

 

            // Do something with text

        }

 

    }

}

The Xaml specifies the function that should be called on click and the code behind goes directly to the text box to get the text. So, there's a fair amount of coupling between the Xaml and the code behind. Now let's say you want the button to be disabled when the TextBox is empty. You might have code behind that watches for the text changing and enables/disables the button. That leads to more coupling. Or, you could write a ValueConverter and bind the enabled state to the text box text passed through the value converter.

But, I want to get the behavior side of things out of the code behind to clearly separate the behavior aspects from the styling. This will make the behavior more unit testable and makes it easier for designers to own the Xaml and developers to own the behavior.

Let's start with a quick example of how to do this with RoutedCommands. A button can be bound to an ICommand. ICommand lets the button know when to be enabled and what to do when it's clicked. RoutedCommand is an ICommand implementation that fires a routed event looking for a CommandBinding definied on an element that handles that command. One other thing that commands support is a command parameter. Here's an example of how we can use a RoutedCommand to handle the scenario above.

First, the code behind:

    public partial class Window1 : Window

    {

        public Window1()

        {

            InitializeComponent();

 

            this.CommandBindings.Add(new CommandBinding(this.MyCommand, OnExecute,

                   OnQueryCommandEnabled));

        }

 

        public RoutedCommand MyCommand

        {

            get { return _myCommand; }

        }

 

        private void OnQueryCommandEnabled(object sender, CanExecuteRoutedEventArgs e)

        {

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

            e.Handled = true;

        }

 

        private void OnExecute(object sender, ExecutedRoutedEventArgs e)

        {

            string text = e.Parameter as string;

 

            // Do something with text

        }

 

        RoutedCommand _myCommand = new RoutedCommand();

    }

Note: We've removed any reference to specific UI elements from the code behind! We set up a RoutedCommand and make it available through a property. And, we've set up a binding pointing to query enabled and execute functions. These functions get the string to operate on through the command parameter.

Now, here's the Xaml:

<Window x:Class="CommandDemo.Window1"

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

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

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

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

    >

    <StackPanel>

      <TextBox Name="_textBox"/>

      <Button Name="_button"

              Command="{Binding MyCommand}"

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

        Do something

      </Button>

    </StackPanel>

</Window>

One key thing I've done here is to set the DataContext of the window to itself. That's what lets Command="{Binding MyCommand}" work in the button. That binding is relative to the DataContext. It sets up the command parameter by binding to the text in the text box.

With this version, the button's enabled state will change when the text field changes. How does that work exactly? Well, ICommand has a CanExecuteChanged event that the button registers for and requeries the enabled state each time it fires. But, when does it fire? With a RoutedCommand, it will fire on any input event. You can also suggest that commands update by calling CommandManager.InvalidateRequerySuggested. So, after each key press or other input event, WPF will call OnQueryCommandEnabled.

So, where are we? We've really cleaned things up from the original version. The code behind no longer knows about any of the UI elements in the Xaml. But, we can go further and completely remove the command behavior from the code behind. That'll be my next post!

Comments

  • Anonymous
    August 09, 2006
    Quite interesting series … One comment though:

    “One key thing I've done here is to set the DataContext of the window to itself.”

    When reading this remark, I cannot help thinking that XAML’s DataContext concept should be duplicated into a ‘Business Layer DataContext’ and a ‘Presentation Layer Data Context’. Indeed, in a business application, the TextBox in your example will most likely be intrinsically ‘Business Layer’ (data context) bound. It looks to me that a conflict in 'data context usage' will arise.

    There are indeed (quite many) other XAML example code cases where such a ‘conflict in data context addressing' is felt (at least in my opinion) when referring to the presentation layer and business layer.

    Thanks for your reaction on this matter ...

  • Anonymous
    August 25, 2006
    I really appreciate this series. I'm working on a WPF application and there isn't much guidance out there. So I really like hearing your stories from the trenches.

    Any chance of getting part six soon? :)

  • Anonymous
    August 31, 2006
    Link to part 6 http://blogs.msdn.com/dancre/archive/2006/08/25/724938.aspx

  • Anonymous
    September 15, 2006
    In part 5, I talked about commands and how they are used for behavior. Now, I want to talk about a better...

  • Anonymous
    September 27, 2006
    If you're doing WPF development, you really need to check out Dan Crevier 's series on DataModel-View-ViewModel.

  • Anonymous
    October 11, 2006
    In part 5 , I talked about commands and how they are used for behavior. Now, I want to talk about a better

  • Anonymous
    October 11, 2006
    I thought I should add a post with the full list of posts in the D-V-VM pattern. They are: DataModel-View-ViewModel

  • Anonymous
    October 16, 2006
    Nice series. Currently I am very interested in if the the button is not only bind to the Text of one TextBox but Text of more than two TextBoxes, say TextBoxUserName and TextBoxPassword, how can I apply Commands to the Login Button? Thanks

  • Anonymous
    October 17, 2006
    Unfortunately, I don't know a good way to do this without some code behind. If anyone has some ideas on this, let me know.