Thursday, May 7, 2009

MVVM invoking command on attached event

When I started my last WPF project using MVVM by referring Josh Smith’s article ,I was very much excited about the way it separates the view and model.But after going some way, I got issues.The reason was how to execute command on events like MouseOver and MouseDown.In the sample which I got,the commands were  just bound to the Button.Command property.It was possible because the Button implements ICommandSource interface.In that case also we can’t invoke command on events other than Button.Click.

After a tough Google ,I found this article in Jaco Karsten’s blog which allows to invoke command on any control any event.The only thing is we have to use an Expression Blend 3 dll named Microsoft.Expression.Interactivity which have its own attached Interaction trigger mechanism along with Jaco’s CommandAction class.Attached trigger mechanism allows us to add a EventTrigger and CommandAction class will invoke a command when the trigger if fired.Life became cool again.Thanks to Jaco.

<Image Width="360"
Height="177"
Source="sample.jpg">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove">
<mvvmjaco:CommandAction Command="{Binding MouseMoveCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>

Implemention of Interaction.Triggers and Command action.Command will execute when the trigger fired


When this comes to Surface,I again saw a hurdle.We can’t invoke command on attached events.For example we can’t invoke command on s:Contacts.ContactDown.

After reflecting the class EventTrigger present in the Microsoft.Expression.Interactivity dll, I came to know that it is using reflection to find out the event from it’s EventName property which is not applicable in the case of attached events.Hence I was forced to create a new trigger which fires on attached event by inheriting EventTriggerBase<T> class.


Implementation of RoutedEventTrigger


A property named RoutedEvent of type RoutedEvent is being added.In the overridable method OnAttached, I attached a handler to the RoutedEvent and in the handler call the OnEvent method present in the base.That in turn calls all the actions associated with that trigger.For more details see the code below



public class RoutedEventTrigger :EventTriggerBase<DependencyObject>
{
RoutedEvent _routedEvent;

public RoutedEvent RoutedEvent
{
get { return _routedEvent; }
set { _routedEvent = value; }
}

public RoutedEventTrigger()
{
}
protected override void OnAttached()
{
Behavior behavior = base.AssociatedObject as Behavior;
FrameworkElement associatedElement = base.AssociatedObject as FrameworkElement;

if (behavior != null)
{
associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
}
if (associatedElement == null)
{
throw new ArgumentException("Routed Event trigger can only be associated to framework elements");
}
if (RoutedEvent != null)
{
associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
}
}
void OnRoutedEvent(object sender,RoutedEventArgs args)
{
base.OnEvent(args);
}
protected override string GetEventName()
{
return RoutedEvent.Name;
}
}


Using RoutedEventTrigger



<Image Width="360" Height="177" Source="Resources\PlayerArea.png">
<i:Interaction.Triggers>
<mvvmjoy:RoutedEventTrigger RoutedEvent="s:Contacts.ContactDown">
<mvvmjaco:CommandAction Command="{Binding TouchCommand}" />
</mvvmjoy:RoutedEventTrigger>
</i:Interaction.Triggers>
</Image>




Executes touch command on Contacts.ContactDown event.

9 comments:

  1. Any idea why your RoutedEventTrigger doesn't work with Validation.Error?

    ReplyDelete
  2. very good, thanks. But i have an question for you. I want to catch event Keydown when Enter was pressed? what to do?


    thanks in advanced!
    ----Thietnt

    ReplyDelete
  3. Ben,
    Could you please expand the scenario?

    Nguyen,
    You can use the KeyDown event in the xaml side and in the cs you can check for the pressed key and do the operation.

    <i:Interaction.Triggers>
    <i:EventTrigger EventName="KeyDown"> <local:CommandAction Command="{Binding HoverOnRecCommand}"/>
    </i:EventTrigger>
    </i:Interaction.Triggers>


    In the executed handler

    private void HoverOverRec_Executed(object parameter)
    {
    KeyEventArgs args=parameter as KeyEventArgs;
    //Process args.Key
    }

    ReplyDelete
  4. Can you tell me how to pass the KeyEventArgs into the HoverOverRec_Execued RelayCommand?

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. Need to change the CommandAction class a bit.I have uploaded the sample into sky drive.Please have a look at that.

    http://cid-890c06c8106550a0.skydrive.live.com/self.aspx/BlogSamples/JoyfulWPF/DemoCommandAction%7C_KeyDown.zip

    ReplyDelete
  7. Awesomely useful - many thanks for that!

    ReplyDelete
  8. you are a life savior!
    I wasn't able to understand why doesn't why attached event is working.
    for some reason I didn't find any other ref or blog talking about it.

    ReplyDelete
  9. Hmm is anyone else encountering problems with the pictures on this blog loading?
    I'm trying to determine if its a problem on my end or if it's the blog.
    Any responses would be greatly appreciated.
    Look into my web blog africanmangoguide.net

    ReplyDelete