Sunday, January 16, 2011

WPF Events to Command redirection using System.Windows.Interactivity

As mentioned previously, I recently used System.Windows.Interactivity library to make a command respond to an event on WPF controls without using any code-behind. In this post, I would give a brief overview and show some code on how to do it. I will try and keep the post to the point and not write anything about hooking up events with code or anything like that. Usual disclaimer applies – I am not entirely familiar with the internals but I know how to make it work and why it works.

So lets start with my simple requirement. I have a text box and as I enter I want to fire a command which processes the text and displays it on a textblock. Of course you can hook up both the controls to the same property in the ViewModel and with .NET 4.0 you can be sure that the getter will fire again when NotifyPropertyChanged is fired. But that is not the point here.

My XAML would simply have a textbox and a textblock. On textbox.TextChanged event fired, I would like to execute a command in my view model. The XAML is shown below.

<UserControl x:Class="Buddi.Training.Advanced.Interactivity.EventToCommandDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Buddi.Training.Infra"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<Grid Background="Beige">
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding SampleCommandParam,UpdateSourceTrigger=PropertyChanged}" Margin="20">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<local:EventToCommand Command="{Binding SampleCommand}"
CommandParameter="{Binding SampleCommandParam}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<TextBlock Text="{Binding Message}" Grid.Row="1" Padding="30" FontFamily="Consolas" FontWeight="14"/>
</Grid>
</UserControl>


You need to see how I am hooking up the event to the command in viewmodel which is the DataContext of the View (UserControl to be precise). Lets disect what we have here - We add an event trigger to the TriggersCollection on the Grid using the Interactivity.Triggers attached properties. An event trigger comes with the System.Windows.Interactivity.dll assembly. So add a reference to that library using the "Add Reference" dialog. The Event Trigger then expects an action that can be anything that derives from the TriggerAction<FrameworkElement> class. The TriggerAction derived class should implement one method called "InvokeCommand(object parameter)". The implementation simply takes care of executing the command which are passed to the DependencyProperty we defined in the EventToCommand class. Note that TriggerAction is a DependencyObject, thereby it allows you define Dependency Properties to take full advantage of the Binding, Styles, Animations and what not. So the trigger action is simple - (the following is a special implementation where I handle RoutedCommand different than the others, this is just my scenario and is typically bad - you are programming to the implementation which is not a good idea, but the plan here is to show how you can use the base.AssociatedObject).


namespace Buddi.Training.Infra
{
public class EventToCommand : TriggerAction<FrameworkElement>
{
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}

public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommand), new UIPropertyMetadata(null));




public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}

// Using a DependencyProperty as the backing store for CommandParameter. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventToCommand), new UIPropertyMetadata(null));



protected override void Invoke(object parameter)
{
if (Command == null) return;
if (Command is RoutedCommand)
{
var rc = Command as RoutedCommand;
if (rc.CanExecute(CommandParameter, base.AssociatedObject))
{
rc.Execute(CommandParameter, base.AssociatedObject);
}
}
else
{
if (Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
}
}
}
}


That's it! you can now program to the events using the commands that you already have. This lets you keep your code-behind clean and write more testable code than ever. I hope this is useful inspite of it not being the best of the articles. By the way, almost every MVVM framework out there provides an implementation of Event To Command action -eg : Caliburn, Cinch, you name it ... but not always it is possible for us to use a third party framework just for this one reason. In such cases, I thought it is good to know that you can acheive it just by using Microsoft's assembly.

5 comments:

Munish said...

Very interesting blog Krishna. Great to see your passion for software. Keep it up!

Munish said...

oh and your SyntaxHighligher plugin still seems to be broken :)

Anonymous said...

Good one but how do I pass the event arguments?

bedampeta said...

Nice one, but how do I pass the
event arguments?

Uday

Anonymous said...

Great Article,

I could get this to work. I am always getting Command is null and hence nothing happens. Do you the full code sample for download.

TIA
Yaz