Sunday, July 11, 2010

Using ICommand for light weight Views in WPF

Routed Commands seems to be great. But as far as writing code on the view is concerned, it seems it doesn't help us making our views more light weight (we still need to provide the definition of CanExecute() and Execute() in our view. As they are on the view, it is not easy to unit test the logic being executed.

A better approach seems to be using ICommand. ICommand allows us to provide command in accordance with Command Pattern. We know that Command Pattern suggests that Tasks should be provided in the form of Command objects and those commands should be used when those tasks need to be executed.

Let us see an example form. In this form there are two text boxes and a button. We want to copy the value of one text box to other when the button is clicked. This form would be using ViewModel and would be binding with its properties. Let us create a form which uses command based on ICommand. It expects its datacontext to provide a command property, named ProcessingCommand.

<Window x:Class="WPF_ICommand.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Height="23" Margin="68,15,12,0" Name="textBox1" VerticalAlignment="Top" Text="{Binding Value1}"/>
<TextBox Height="23" Margin="68,56,12,0" VerticalAlignment="Top" Text="{Binding Value2}" />
<Label Height="28" Margin="12,10,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50">Value1</Label>
<Label Height="28" HorizontalAlignment="Left" Margin="12,53,0,0" VerticalAlignment="Top" Width="50">Value2</Label>
<Button Height="23" Margin="68,95,134,0" VerticalAlignment="Top" Command="{Binding ProcessingCommand}">Copy</Button>
</Grid>
</Window>


As you can see above, both text boxes are bound with some properties in the Datacontext. Button is also bound with a command in the DataContext, named ProcessingCommand. The code behind of this form is as follows:

public partial class Window1 : Window
{
Window1ViewModel _vm;
public Window1()
{
InitializeComponent();
this._vm = new Window1ViewModel();
this.DataContext = _vm;
}
}

As you can see we have used Window1ViewModel as the DataContext of the form. Now we have a look at how ViewModel (Window1ViewModel) fulfills all these expectations of Window1.

class Window1ViewModel : DependencyObject
{
public string Value1
{
get { return (string)GetValue(Value1Property); }
set { SetValue(Value1Property, value); }
}
private static DependencyProperty Value1Property =
DependencyProperty.Register("Value1", typeof(string), typeof(Window1ViewModel));

public string Value2
{
get { return (string)GetValue(Value2Property); }
set { SetValue(Value2Property, value); }
}
private static DependencyProperty Value2Property =
DependencyProperty.Register("Value2", typeof(string), typeof(Window1ViewModel));

public MyCommand ProcessingCommand {get; set;}

public Window1ViewModel()
{
this.ProcessingCommand = new MyCommand(this);
}
}

As you can see, our view model is a DependencyObject which has allowed us to register its properties Value1 and Value2 with WPF property system. We have also defined a command property named ProcessingCommand. This is the same command that we have specified with the button in the view.

Now at last, we provide the definition of the command, MyCommand.

class MyCommand : ICommand
{
#region ICommand Members

Window1ViewModel _vm;
public MyCommand(Window1ViewModel _vm)
{
this._vm = _vm;
}

bool ICommand.CanExecute(object parameter)
{
bool ret = true;
return ret;
}

event EventHandler ICommand.CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

void ICommand.Execute(object parameter)
{
_vm.Value2 = _vm.Value1;
}

#endregion
}

In order to implement ICommand we need to implement CanExecute and Execute method. We also need to provide the EventHandler CanExecuteChanged. You can see that we are using Window1ViewModel for MyCommand to execute its task.

When we run this application. We can notice that when user writes some text in first text box and clicks Copy button then the value of first text box is copied into second text box. For doing this Execute method of MyCommand is used. You might have noticed that CanExecute method just returns true. We might have some logic which makes this task not available. In all those conditions CanExecute should return false.

No comments: