A question came up on the Stack Overflow website a little while back about how to execute a command when an event is raised. The questioner, Brad Leach, is using a Model-View-ViewModel architecture for WPF (of which I'm also a fan) and wanted to know how he could hook up a Command on his ViewModel to the TextChanged event of a TextBox in his View. In his case, it turned out that he got what he needed by Data-binding the Text property of the TextBox to a property on the ViewModel; in my answer to his question I outlined an approach that can be used in other cases where properties are not involved, so that the data-binding trick doesn't work. Now I've gone one better, and created a a utility class, EventBehaviourFactory, that makes it easy to hook up any WPF event to a Command using Data Binding.
Using EventBehaviourFactory you define new attached properties, one for each kind of event you want to hook a Command to. Then you set the value of the property on a WPF element, specifying the command that you want to be executed. When the event is raised, the command will be executed, with the EventArgs being passed through as the command parameter.
Using the EventBehaviourFactory
First of all, you need to include my EventBehaviourFactory class in your project. You can either copy the code from the bottom of this post, or get the whole project from MSDN Code Gallery.
Then you need to define a static class to hold a new Attached property that you will attach to an object to specify which command to execute when a particular event is raised. You need to call EventBehaviourFactory.CreateCommandExecutionEventBehaviour and pass it the routed event that you want to handle - note you must pass the static field holding the RoutedEvent object (usually the same name as the event itself, but ending in "Event") not the event property. Just as when creating a standard attached property, you also need to pass the name of the property as a string (this should match the name of the static field that you store the DependencyProperty in), and the type of the class that is defining the property.
As an example, I'll create a property that will execute a Command when the TextChanged event of a TextBox is raised:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace FunctionalFun.Wpf
{
public static class TextBoxBehaviour
{
public static readonly DependencyProperty TextChangedCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(TextBox.TextChangedEvent, "TextChangedCommand", typeof (TextBoxBehaviour));
public static void SetTextChangedCommand(DependencyObject o, ICommand value)
{
o.SetValue(TextChangedCommand, value);
}
public static ICommand GetTextChangedCommand(DependencyObject o)
{
return o.GetValue(TextChangedCommand) as ICommand;
}
}
}The important part is in line 9, where I'm calling EventBehaviourFactory. This is the part that creates the attached behaviour. In the download, I've included a Resharper template to mostly automate this step. To make use of the property you just set a value for it on the object whose events interest you, binding it to the appropriate command. Here's an example:
<Window x:Class="FunctionalFun.Wpf.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ff="clr-namespace:FunctionalFun.Wpf"
Title="Window1" Height="300" Width="300">
<Window.DataContext>
<ff:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox ff:TextBoxBehaviour.TextChangedCommand="{Binding TextChanged}"/>
</Grid>
</Window>
The ViewModel that this Window uses (initialised in line 7) is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace FunctionalFun.Wpf
{
public class ViewModel
{
public ICommand TextChanged
{
get
{
// this is very lazy: I should cache the command!
return new TextChangedCommand();
}
}
private class TextChangedCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Text Changed");
}
public bool CanExecute(object parameter)
{
return true;
}
}
}
}
How EventBehaviourFactory works
EventBehaviourFactory uses the attached behaviour technique. Basically, it creates new Attached properties, one for each kind of event that you ask it to handle. The attached property lets you specify which command you want to be executed when the event is raised. The attached property is configured with a PropertyChanged handler (wrapped up in the private ExecuteCommandOnRoutedEventBehaviour class) that starts listening to the appropriate event on an object whenever the attached property is set on the object - and stops listening if the attached property is set to null.
As you read the code below, you might wonder why I have factored out the ExecuteCommandBehaviour class. That was because I was trying to include a way of handling non-routed events in this Factory as well. I ran into a couple of problems, however, so I took it. I'm not sure how useful it would have been anyway, because I couldn't find terribly many non-routed events in WPF. If this would be useful to anybody, let me know, and I'll have another stab at it.
The whole project is available on MSDN Code Gallery. There are a couple of example properties in there, and even a trio of unit tests. As a bonus, you get a Resharper template
using System;
using System.Windows;
using System.Windows.Input;
namespace FunctionalFun.Wpf
{
public static class EventBehaviourFactory
{
public static DependencyProperty CreateCommandExecutionEventBehaviour(RoutedEvent routedEvent, string propertyName, Type ownerType)
{
DependencyProperty property = DependencyProperty.RegisterAttached(propertyName, typeof (ICommand), ownerType,
new PropertyMetadata(null,
new ExecuteCommandOnRoutedEventBehaviour(routedEvent).PropertyChangedHandler));
return property;
}
/// <summary>
/// An internal class to handle listening for an event and executing a command,
/// when a Command is assigned to a particular DependencyProperty
/// </summary>
private class ExecuteCommandOnRoutedEventBehaviour : ExecuteCommandBehaviour
{
private readonly RoutedEvent _routedEvent;
public ExecuteCommandOnRoutedEventBehaviour(RoutedEvent routedEvent)
{
_routedEvent = routedEvent;
}
/// <summary>
/// Handles attaching or Detaching Event handlers when a Command is assigned or unassigned
/// </summary>
/// <param name="sender"></param>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
protected override void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue)
{
UIElement element = sender as UIElement;
if (element == null) { return; }
if (oldValue != null)
{
element.RemoveHandler(_routedEvent, new RoutedEventHandler(EventHandler));
}
if (newValue != null)
{
element.AddHandler(_routedEvent, new RoutedEventHandler(EventHandler));
}
}
protected void EventHandler(object sender, RoutedEventArgs e)
{
HandleEvent(sender, e);
}
}
internal abstract class ExecuteCommandBehaviour
{
protected DependencyProperty _property;
protected abstract void AdjustEventHandlers(DependencyObject sender, object oldValue, object newValue);
protected void HandleEvent(object sender, EventArgs e)
{
DependencyObject dp = sender as DependencyObject;
if (dp == null)
{
return;
}
ICommand command = dp.GetValue(_property) as ICommand;
if (command == null)
{
return;
}
if (command.CanExecute(e))
{
command.Execute(e);
}
}
/// <summary>
/// Listens for a change in the DependencyProperty that we are assigned to, and
/// adjusts the EventHandlers accordingly
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void PropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// the first time the property changes,
// make a note of which property we are supposed
// to be watching
if (_property == null)
{
_property = e.Property;
}
object oldValue = e.OldValue;
object newValue = e.NewValue;
AdjustEventHandlers(sender, oldValue, newValue);
}
}
}
}


7 comments:
That is what is was looking for. Great example that helps in complete decoupling between View and ViewModel.
Great Thanks
This is great. Just started a new project using the Model-View-ViewModel pattern and this has been a great helper class. Thanks!
humm, this is really interesting.
After playing around with the example a little, I realized that now the dependency graph is
Model <--- ViewModel ---> View.
So all of code for extracting and converting the data from the view (for non-trival view-model data mappings) is now done inside the ViewModel.
Since the View already has knowledge of ViewModel (what properties to bind to), wouldn't it be better if the ViewModel presented an interface that was extremely similar to whats in the view (usually trival to extract and present) and then pass that data to the ViewModel?
Or is it better to always have the ViewModel do 100% of the work and keep the View as dumb as possible?
Another thought I had was why you have the SetXXXCommand in the XXXBehavior class? Doesn't the binding pretty much limit which ICommand can be used by any given behavior?
Thanks for the code!
Ben
bensch (turn the words into digits) one two eight at yahoo dot com
Ben,
Glad you found it interesting.
Have you drawn your dependency diagram correctly? It seems to indicate that the ViewModel depends on the View, which is not the case.
You're right that this example does help with putting even more of the behaviour inside the ViewModel. I try to do that as much as possible. I think it's fair to say that none of the Views in my current project have any code-behind.
By the way, if you want to see even more powerful examples of hooking up events in the View to methods in the ViewModel, have a look at the Caliburn framework:
http://tinyurl.com/cg8tlc
I'm now able to handle events easily by combing your class with Josh Smith's version of MVVM.
Thanks for this post...
Sam,
This looks really promising but I ran into this:
public static readonly DependencyProperty DropDownClosedCommand = EventBehaviorFactory.CreateCommandExecutionEventBehavior(ComboBox.DropDownClosed, "DropDownClosedCommand", typeof(ComboBoxBehavior));
ComboBox does not expose the DropDownClosedEvent.
Any Ideas?
Thanks in advance,
Eric.
Eric,
I think the problem is that DropDownClosed is a standard event rather than a routed event - my code is only designed to work with routed events. You would need to create a custom version of the behaviour specially for handling that event on ComboBoxes. Sorry I can't be more help than that at the moment!
Post a Comment
If you have trouble posting a comment, try pressing the Preview button first - works for me!