Geeks With Blogs

News Locations of visitors to this page
Brian Genisio's House of Bilz

More Adventures in MVVM Shout it kick it on DotNetKicks.com

One of the biggest points of friction for me when implementing the MVVM pattern happens when I need to bind commands to events.  When using Prism, I get the Button.Click command binding out of the box, but every other event needs to be wired up individually.  Doing this requires a LOT of boilerplate code that is very easy to get wrong.  In my last post, I published some code to alleviate that pain.  Still, it requires you to write a new behavior and attachment for every event that you want to bind an event to.

For a while now, I have toyed with the idea of just binding commands to events directly.  I ran into a lot of bumps in the road.  For instance, every event handler has a different event argument type.  This requires all of the handlers to be dynamic.  I also couldn’t create an inline command binding – I will surely want to bind more than one event per control – so I need to create a collection of bindings.  Creating arrays of structures created its own troubles – binding only works with FrameworkElements within the visual tree.  This required me to write my own crude binding within my generic behavior.

What follows is very loosely based off of the Chinch MVVM framework.  I tested this code in Silverlight and WPF and it works really well!

Assume I have a ViewModel that looks like this:

public class MainPageViewModel : INotifyPropertyChanged
{
    ...
    public ICommand MouseLeaveCommand { get; private set; }
    public ICommand MouseEnterCommand { get; private set; }
    public ICommand ClickCommand { get; private set; }
    ...
}

I can then bind the commands to events on a control (Button, for instance):

<Button Content="Click Me">
    <Behaviors:Events.Commands>
        <Behaviors:EventCommandCollection>
            <Behaviors:EventCommand CommandName="MouseEnterCommand" EventName="MouseEnter" />
            <Behaviors:EventCommand CommandName="MouseLeaveCommand" EventName="MouseLeave" />
            <Behaviors:EventCommand CommandName="ClickCommand" EventName="Click" />
        </Behaviors:EventCommandCollection>
    </Behaviors:Events.Commands>
</Button>

I no longer need to write any extra code whenever I want to attach commands to my events!  There are a few caveats to this code:

  1. The XAML requires the EventCommandCollection to be declared in the XAML.  I struggled to figure out how to eliminate this but gave up.  Someone smarter than me might be able to tell me what I am doing wrong.
  2. This code does not consider command properties.  Every command assumes a null parameter.  If you need parameters (like data context), then you’ll have to do something differently (either use the old-school mechanism or extend this code to handle some special event types).
  3. You don’t bind directly to the command.  Instead, you declare the name of the command (Notice CommandName is not bound).  The behavior binds for you using a primitive mechanism.

Here is the command behavior that does all the work:

public class Events
{
    private static readonly DependencyProperty EventBehaviorsProperty =
        DependencyProperty.RegisterAttached(
        "EventBehaviors",
        typeof(EventBehaviorCollection),
        typeof(Control),
        null);

    private static readonly DependencyProperty InternalDataContextProperty =
        DependencyProperty.RegisterAttached(
        "InternalDataContext",
        typeof(Object),
        typeof(Control),
        new PropertyMetadata(null, DataContextChanged));

    private static void DataContextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var target = dependencyObject as Control;
        if (target == null) return;

        foreach (var behavior in GetOrCreateBehavior(target))
            behavior.Bind();
    }

    public static readonly DependencyProperty CommandsProperty =
        DependencyProperty.RegisterAttached(
        "Commands",
        typeof(EventCommandCollection),
        typeof(Events),
        new PropertyMetadata(null, CommandsChanged));

    public static EventCommandCollection GetCommands(DependencyObject dependencyObject)
    {
        return dependencyObject.GetValue(CommandsProperty) as EventCommandCollection;
    }

    public static void SetCommands(DependencyObject dependencyObject, EventCommandCollection eventCommands)
    {
        dependencyObject.SetValue(CommandsProperty, eventCommands);
    }

    private static void CommandsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var target = dependencyObject as Control;
        if (target == null) return;

        var behaviors = GetOrCreateBehavior(target);
        foreach (var eventCommand in e.NewValue as EventCommandCollection)
        {
            var behavior = new EventBehavior(target);
            behavior.Bind(eventCommand);
            behaviors.Add(behavior);
        }

    }

    private static EventBehaviorCollection GetOrCreateBehavior(FrameworkElement target)
    {
        var behavior = target.GetValue(EventBehaviorsProperty) as EventBehaviorCollection;
        if (behavior == null)
        {
            behavior = new EventBehaviorCollection();
            target.SetValue(EventBehaviorsProperty, behavior);
            target.SetBinding(InternalDataContextProperty, new Binding());
        }

        return behavior;
    }
}

public class EventCommand
{
    public string CommandName { get; set; }
    public string EventName { get; set; }
}

public class EventCommandCollection : List<EventCommand>
{
}

public class EventBehavior : CommandBehaviorBase<Control>
{
    private EventCommand _bindingInfo;

    public EventBehavior(Control control)
        : base(control)
    {

    }

    public void Bind(EventCommand bindingInfo)
    {
        ValidateBindingInfo(bindingInfo);

        _bindingInfo = bindingInfo;

        Bind();
    }

    private void ValidateBindingInfo(EventCommand bindingInfo)
    {
        if(bindingInfo == null) throw new ArgumentException("bindingInfo");
        if (string.IsNullOrEmpty(bindingInfo.CommandName)) throw new ArgumentException("bindingInfo.CommandName");
        if (string.IsNullOrEmpty(bindingInfo.EventName)) throw new ArgumentException("bindingInfo.EventName");
    }

    public void Bind()
    {
        ValidateBindingInfo(_bindingInfo);
        HookPropertyChanged();
        HookEvent();
        SetCommand();
    }

    public void HookPropertyChanged()
    {
        var dataContext = TargetObject.DataContext as INotifyPropertyChanged;
        if (dataContext == null) return;

        dataContext.PropertyChanged -= DataContextPropertyChanged;
        dataContext.PropertyChanged += DataContextPropertyChanged;
    }

    private void DataContextPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _bindingInfo.CommandName)
            SetCommand();
    }

    private void SetCommand()
    {
        var dataContext = TargetObject.DataContext;
        if (dataContext == null) return;

        var propertyInfo = dataContext.GetType().GetProperty(_bindingInfo.CommandName);
        if (propertyInfo == null) throw new ArgumentException("commandName");

        Command = propertyInfo.GetValue(dataContext, null) as ICommand;
    }

    private void HookEvent()
    {
        var eventInfo = TargetObject.GetType().GetEvent(
            _bindingInfo.EventName, BindingFlags.Public | BindingFlags.Instance);
        if (eventInfo == null) throw new ArgumentException("eventName");

        eventInfo.RemoveEventHandler(TargetObject, GetEventMethod(eventInfo));
        eventInfo.AddEventHandler(TargetObject, GetEventMethod(eventInfo));
    }

    private Delegate _method;
    private Delegate GetEventMethod(EventInfo eventInfo)
    {
        if (eventInfo == null) throw new ArgumentNullException("eventInfo");
        if (eventInfo.EventHandlerType == null) throw new ArgumentException("EventHandlerType is null");

        if (_method == null)
        {
            _method = Delegate.CreateDelegate(
                eventInfo.EventHandlerType, this,
                GetType().GetMethod("OnEventRaised",
                BindingFlags.NonPublic | BindingFlags.Instance));
        }

        return _method;
    }

    private void OnEventRaised(object sender, EventArgs e)
    {
        ExecuteCommand();
    }
}

public class EventBehaviorCollection : List<EventBehavior>
{ }
Posted on Thursday, August 27, 2009 1:27 PM | Back to top


Comments on this post: Adventures in MVVM – Binding Commands to ANY Event

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
I thoroughly recommend that you investigate Caliburn. Here's a link to the TOC: http://caliburn.codeplex.com/Wiki/View.aspx?title=Table%20Of%20Contents Be sure to read the section on actions (note the short behavior). You may also find the section on IResult interesting.
Left by Rob on Aug 29, 2009 11:31 AM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
Is there and advantages to this method over prism's event aggragator?

The more I lean about EA; the more I start to think that's the only way I'll ever do events in silverlight. Maybe it's because I'm coming from a heavy JavaScript background but I love the unobtrusive nature of the EA pattern.
Left by Eric Polerecky on Aug 29, 2009 11:46 AM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
@Eric: I am quite fond of the Event Aggregator as well... I use it and find it extremely useful. Is there a way to bind events in XAML?

I use it to communicate from ViewModel to ViewModel, but when I want to execute a command in the ViewModel that is bound to the View, I find this mechanism really useful. Mostly because I don't want to broadcast that a button was clicked in the EA. I want the mouse click event to bind to a command in my ViewModel, and let the ViewModel decide what it means to have the command be executed. If the ViewModel wants to send an event to other components using the EA, then it can do so.

Do you use the EventAggregator differently?
Left by Brian Genisio on Aug 29, 2009 11:59 AM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
@Rob: My biggest problem with Caliburn is my Lawyers. I work for a big company and open source software is a bear to get past them. (You should have seen the hoops I needed to jump through to use SQLite.) I got them to be OK with Prism because it was put out by Microsoft. I don't like this limitation, but it "is what it is". Because of this, I blog from the perspective... getting MVVM to work from the basic tools. Although, it is starting to get tiresome to be doing this much plumbing.

That being said, I have spent some time looking into Caliburn, I've got to say that it looks nice. When I give my MVVM talk, I include it as an option for third-party support for MVVM (along with the MVVM Lite toolkit, SilverlightFX and Prism).

It has been my plan for several months to dig into Caliburn and really get to know it. It might not be something I can use at my job, but I think it is something that I should be more familiar with. I will be sure to blog about it when that happens.

Thanks for the tip!
Left by Brian Genisio on Aug 29, 2009 12:56 PM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
With respect to your concerns:

1.The XAML requires the EventCommandCollection to be declared in the XAML.

I would have thought you could declare EventCommandCollection to be the “Content” property by marking the class with the ContentPropertyAttribute. This works even for collection properties.

I know you can do it in WPF … see http://msdn.microsoft.com/en-us/library/ms752059.aspx#contentmodels and http://msdn.microsoft.com/en-us/library/system.windows.markup.contentpropertyattribute.aspx.

We see examples of it all the time in SL, e.g., StackPanel (you never have to say <StackPanel.Children> … it’s implied).

2.This code does not consider command properties.

A good omission in my opinion. I don’t like command parameters. First you have one .. then you want a second. It’s all downhill. Instead, if there is a parameter to pass to the VM from the View, it should be done by binding the parameter source to a VM property. David Hill agrees … for what that is worth.

3.You don’t bind directly to the command. Instead, you declare the name of the command (Notice CommandName is not bound).

Inescapable … like all the other string binding that goes on in XAML. At least you get Intellisense with some things you do; not here. Still I dislike all of the magic string madness. Nothing to be done that I can see.

Of course you could provide a binding alternative that would yield to Intellisense. The extra syntax doesn’t seem worth it to me.
Left by Ward Bell on Aug 31, 2009 7:55 AM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
Found this after a similar article of Brent Edwards - "Using WPF’s ContentControl to Switch Between Editable and Read-Only Mode >>Generic Command Binding with WPF and prism", which code I could not get to work. Yours does, thanks, what I miss unfortunately is a CommandParameter , possible?
Left by Ben Geerdes on Jul 13, 2010 8:10 PM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
Ok managed to get the parameter working:

changed the OnEventRaised:

private void OnEventRaised(object sender, EventArgs e)
{
EventParameters par = new EventParameters();
par.Sender = sender;
par.e = e;
par.Parameter = this._bindingInfo.Parameter;
this.Command.Execute(par);

//ExecuteCommand();
}

added class EventParameters :

public class EventParameters
{
public object Sender;
public EventArgs e;
public object Parameter;
//public Dictionary<String,object> Parameters;
//public EventParameters()
//{
// Parameters = new Dictionary<String,object>();
//}
}

Changed class EventCommand:

public class EventCommand
{
public string CommandName { get; set; }
public string EventName { get; set; }
public object Parameter { get; set; }
}

allowing in xaml:

<uicore:EventCommand CommandName="ContactChecked" EventName="Click" Parameter="test1" Parameter="test2"/>

Now what I really would like is to continue the hierarchy like this:

<Button Content="Click Me">
<Behaviors:Events.Commands>
<Behaviors:EventCommandCollection>
<Behaviors:EventCommand CommandName="ClickCommand" EventName="Click">
<Behaviors:EventParameter ParameterName="Contact" ParameterValue="{Binding ContactInViewModel}">
<Behaviors:EventParameter ParameterName="Address" ParameterValue="{Binding AddressInViewModel}">
<Behaviors:EventParameter ParameterName="etc" ParameterValue="{Binding ToWhatEver}">
</Behaviors:EventCommandCollection>
</Behaviors:Events.Commands>
</Button>

And instead of a single Parameter property on the EventParameters I would have a dictionary of parameters....

But I have no idea on what is exactly needed for that, could use a hint in making clear what in the currect sample code defines what is possible in xaml.
Left by Ben Geerdes on Jul 13, 2010 9:58 PM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
Slightly off topic but on the subject of disliking magic strings... This is the nicest way I’ve found so far of eliminating magic strings for
property names in the INotifyPropertyChanged. Used it before, never done any
performance testing on it because even in large complex apps there was no
noticeable performance hit anyway.
 
 
You have an extension method on Property to create the event args using
expression trees like this...
 
    public static class PropertyExtensions
    {
        public static PropertyChangedEventArgs CreateChangeEventArgs<T>(this
Expression<Func<T>> property)

        {
            var expression = property.Body as MemberExpression;
            var member = expression.Member;
            return new PropertyChangedEventArgs(member.Name);
        }
    }
 
then in your base view model class, you use this in the OnPropertyChanged
(aswell as an overload taking a string so as not to break existing code)
 
    ///<summary>
    ///Provides common functionality for ViewModel classes
    ///</summary>
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        protected void OnPropertyChanged<T>(Expression<Func<T>> property)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, property.CreateChangeEventArgs());
            }
        }
 
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
 
And you use it like so in your view model...
 
        private SupportedLanguage selectedSupportedLanguage = new
SupportedLanguage();
        public SupportedLanguage SelectedSupportedLanguage
        {
            get
            {
                return selectedSupportedLanguage;
            }
            set
            {
                if (selectedSupportedLanguage == value)
                    return;
                selectedSupportedLanguage = value;
                OnPropertyChanged(() => SelectedSupportedLanguage);
            }
        }
Left by Tony Vaughan on Sep 05, 2010 12:02 PM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
I managed to get binding working with the commands and paramater:

Changes to EventCommand:
public class EventCommand : DependencyObject
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommand), new PropertyMetadata(null));

public static readonly DependencyProperty ParameterProperty =
DependencyProperty.Register("Parameter", typeof(object), typeof(EventCommand), new PropertyMetadata(null));

public string EventName { get; set; }

public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}

public object Parameter
{
get
{
return (object)GetValue(ParameterProperty);
}
set
{
SetValue(ParameterProperty, value);
}
}
}

Lastly change EventCommandCollection to a DependencyObjectCollection:
public class EventCommandCollection : DependencyObjectCollection<EventCommand> { }

Changes to EventBehavior:
public void Bind()
{
ValidateBindingInfo(_bindingInfo);
HookPropertyChanged();
HookEvent();
SetCommand();
SetCommandParameter();
}

private void DataContextPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != _bindingInfo.EventName)
{
SetCommand();
SetCommandParameter();
}
}

private void SetCommand()
{
Command = _bindingInfo.Command;
}

private void SetCommandParameter()
{
CommandParameter = _bindingInfo.Parameter;
}

private void OnEventRaised(object sender, EventArgs e)
{
ExecuteCommand();
}
Left by Brendan Cockman on Jan 11, 2011 5:06 PM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
Thank you for posting this approach. It's a kind of annoying to create a new behaviors each time I need to bind to specific event.
Left by Ilya on Mar 30, 2011 10:59 PM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
Hi Brian,

I've tried to compile your code but I got the following problem as error in compile time, I just wonder if got any solution for me:

1. there is noreferences for type: CommandBehaviorBase

2. no declaration for the following objects in the class:
TargetObject
Command
BindingFlags

3. there isn't a method for " ExecuteCommand();"



Left by Farzad Jalali on May 15, 2011 8:30 PM

# re: Adventures in MVVM – Binding Commands to ANY Event
Requesting Gravatar...
In case anyone need it, the CommandBehaviorBase is based off of the Prism 2.x library. Not sure if it carried over to 4.0 or not, but the base class can be found here: http://compositewpf.codeplex.com/SourceControl/changeset/view/52595#1004651
Left by CommandBehaviorBase<T> on May 15, 2011 9:29 PM

Your comment:
 (will show your gravatar)
 


Copyright © Brian Genisio's House Of Bilz | Powered by: GeeksWithBlogs.net | Join free