24 July 2012

A WinRT behavior to mimic EventToCommand

This is deprecated. Please use the official behaviors SDK now. See here for a simple migration guide

Updated September 2 2012 with two sample applications

Whoever used the MVVMLight framework by Laurent Bugnion is familiar with EventToCommand, especially those who used it on Windows Phone before the advent of 7.5 (or SDK version 7.1.1). This utility enables triggers to fire commands on the viewmodel

Unfortunately, in WinRT there is no such thing as a trigger, so this method cannot be used, and MVVMLight for WinRT therefore does not include EventToCommand. This morning I got a DM from AESIR Consultancy asking me if I had a solution for that, because he had some trouble porting a Windows Phone app to Windows 8. I did not have it then, but I had the feeling it could be done quite easily. In the afternoon, in the time between I came home and my wife came home from work I wrote this behavior that basically does the same as EventToCommand.

And indeed, WinRT does not support behaviors either, but that has been solved already. In order for this to work, you will need to download the NuGet WinRtBehaviors package

Anyway – the behavior. You start out with a project with references to WinRtBehaviors and the Reactive extensions – ReSharper 7 EAP found them readily and automatically attached references to my projects which now has the following references to reactive stuff:

  • System.Reactive.Core
  • System.Reactive.Interfaces
  • System.Reactive.Linq
  • System.Reactive.PlatformServices

So we start out with the class definition and some boring Dependency property definitions:

using System;
using System.Reactive.Linq;
using System.Reflection;
using System.Windows.Input;
using WinRtBehaviors;
using Windows.UI.Xaml;

namespace Win8nl.Behaviors
{
  /// <summary>
  /// A behavior to imitate an EventToCommand trigger
  /// </summary>
  public class EventToCommandBehavior : Behavior<FrameworkElement>
  {

    #region Event

    /// <summary>
    /// Event Property name
    /// </summary>
    public const string EventPropertyName = "Event";

    public string Event
    {
      get { return (string)GetValue(EventProperty); }
      set { SetValue(EventProperty, value); }
    }

    /// <summary>
    /// Event Property definition
    /// </summary>
    public static readonly DependencyProperty 
      EventProperty = DependencyProperty.Register(
        EventPropertyName,
        typeof(string),
        typeof(EventToCommandBehavior),
        new PropertyMetadata(default(string)));

    #endregion

    #region Command

    /// <summary>
    /// Command Property name
    /// </summary>
    public const string CommandPropertyName = "Command";

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

    /// <summary>
    /// Command Property definition
    /// </summary>
    public static readonly DependencyProperty 
      CommandProperty = DependencyProperty.Register(
        CommandPropertyName,
        typeof(string),
        typeof(EventToCommandBehavior),
        new PropertyMetadata(default(string)));

    #endregion

    #region CommandParameter

    /// <summary>
    /// CommandParameter Property name
    /// </summary>
    public const string CommandParameterPropertyName = "CommandParameter";

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

    /// <summary>
    /// CommandParameter Property definition
    /// </summary>
    public static readonly DependencyProperty 
        CommandParameterProperty = DependencyProperty.Register(
        CommandParameterPropertyName,
        typeof(object),
        typeof(EventToCommandBehavior),
        new PropertyMetadata(default(object)));

    #endregion
  }
}

All very boring, all very standard. Now, for dynamically adding a listener to the event: I’ve been down this road before, say hello to our friend Observable.FromEventPattern, which takes away all the boring details about the differences between ordinary and WinRT events.

protected override void OnAttached()
{
  var evt = AssociatedObject.GetType().GetRuntimeEvent(Event);
  if (evt != null)
  {
    Observable.FromEventPattern<RoutedEventArgs>(AssociatedObject, Event)
      .Subscribe(se => FireCommand());
  }
  base.OnAttached();
}

Nothing special. Try to find an event with the same name as the contents of the “Event” property and add a listener to it. And the final piece of the puzzle is the actual implementation of FireCommand. Of course, I could have implemented this inline but I like to make this a separate method, if only for readability:

private void FireCommand()
{
  var dataContext = AssociatedObject.DataContext;
  if (dataContext != null)
  {
    var dcType = dataContext.GetType();
    var commandGetter = dcType.GetRuntimeMethod("get_" + Command, new Type[0]);
    if (commandGetter != null)
    {
      var command = commandGetter.Invoke(dataContext, null) as ICommand;
      if (command != null)
      {
        command.Execute(CommandParameter);
      }
    }
  }
}

As always, when you know how to do it, it’s very simple. This method gets the AssociatedObject’s data context, the tries to find the getter of the Command property on this data context. As you might remember, a command in MVVMLight is defined like this:

public ICommand DoSomethingCommand
{
  get
  {
    return new RelayCommand<string>((p) =>
        {
          System.Diagnostics.Debug.WriteLine("Hi there {0}", p);
        });
  }
}

so I am not looking for a command method, but for a getter returning a command. If the code finds that, it tries to Invoke the getter as an ICommand. If it gets that it has the actual command, and it executes it. And that’s all!

So if you want to use this behavior, attach it to a Page’s main grid, for instance:

<Grid Style="{StaticResource LayoutRootStyle}" x:Name="TopView" x:Uid="TopGrid">
   <WinRtBehaviors:Interaction.Behaviors>
    <Win8nl_Behavior:EventToCommandBehavior Event="Tapped"
Command="DoSomethingCommand"
CommandParameter="John doe"/> </WinRtBehaviors:Interaction.Behaviors> <!--- stuff in here --> </Grid>

The top of your page needs to include this name space definitions:

xmlns:WinRtBehaviors="using:WinRtBehaviors"
xmlns:Win8nl_Behavior="using:Win8nl.Behaviors"

and then it will work. Tap on the main grid and you will see the text “Hi there John doe” in the Visual Studio output window. Of course you can make it do more useful things too ;-) and you can also bind things to the CommandParameter property using the normal binding syntax. To a certain extent.

You can download the behavior’s code here, but it's actually a lot easier to just use the NuGet Package. The behavior is there, along with some more nice stuff, and it takes care of downloading WinRtBehaviors and Reactive Extensions for WinRT as well

By special request I've also created sample code - not one but two solutions. The first one, TestWin8nl, is a very simple application that allows you to tap a TextBlock - this will trigger a command in the ViewModel that uses Debug.WriteLine to print a property of the ViewModel in your output console windows, using the CommandParameter to transfer the bound object (the ViewModel itself) to the command. The seconds sample, EventToCommandListDemo, will show you how to handle a SelectionChanged event of a ListBox, although frankly I'd rather just bind SelectedItem and act on it's property setter. But the audience asks, the audiences gets ;-). It appears tough as if Element name binding does not work properly in WinRtBehaviors, so I had to use a ViewModel property to store the selected item in anyway. Both are some contrived examples, but I hope the will do the trick of helping you out understanding how things are working and are supposed to be used.

Update: I have also blogged (and published in win8nl) a variant of the behavior that takes a bound command in stead of a command name. I personally now think this is a better solution as it adheres more to the way regular behaviors work, and is much more flexible Update 2: both this behavior and it's the bound-command-behavior are in the win8nl nuget package. Don't type, be lazy, use Nuget ;-)

28 comments:

Sean Sparkman said...

How would you do a SelectionChanged type event. I am new XAML and MVVM and starting out in Windows 8. Probably not the best idea for a place to start but anyway.




return new RelayCommand((item) => { if(item != null) { // do something; });

I am not sure that makes sense. I was trying to simplify due to the lack of format of the comment system. Basically, I want to be able access the selected item and navigate based on that. Item is being sent as null.

Joost van Schaik said...

@Sean well add the behavior to or instance ListBox, put "SelectionChanged" in "EventName", bind "SelectedObject" to CommandParameter and make a RelayCommand like
return new RelayCommand((item) => { if(item != null) { // do something; });
Where T is the type of your bound object.

Do you get what I mean, or do you need a sample solution?

radium said...

Hi Joost, I was wondering if you could post an example of the solution to the problem Sean mentioned. I'm running into the same problem and only seeing nulls make their way through to the command.

Thanks.

Joost van Schaik said...

@Sean, @radium: I've updated the article with two sample solution links. I hope they will show you more clearly how things work and how the should and can be used.

radium said...

Thanks, that cleared up the issue for me!

Fabien Ruffin said...

Hi Joost,

Thanks a lot for this, this is great.
I just have a question though, I would you bind a command to the ItemClick event of a Gridview? I managed to bind the command, but not the clicked item as a parameter.

Thanks!

Sean OC said...

Hi Joost, thank you for your work in this area. I am having an issue with the use of EventToCommandBehaviour.

I have textblocks contained in a gridView, and the TextBlock definition within the DataTemplate for the gridView defines a EventToCommandBehaviour is defined and bound to a command in viewModel.

The command does not get executed when the textblock is clicked. I can use the same textBlock definition outside of a gridview and it works.

Any idea what I am doing wrong. All help is greatly appreciated.

Thanks.

Joost van Schaik said...

@sean any change of you sending me an as small as possible solution demonstrating the problem?

Joost van Schaik said...

@OzFab I am afraid you don't. For the command parameter you need something to bind to. But it would be fairly easy to write a behavior that does what you want based upon the code I wrote

Unknown said...

Hello

I am trying to use a Gridview as a menu to navigate to other pages.

I have added following to the gridview:






The event is fired but i dont know how to bind it properly.

public ICommand WelcomeTileClick
{
get
{
return new RelayCommand(async (p) =>
{
if (p != null)
{
var msg = new MessageDialog(string.Format("Hi there {0}", p.Title));
await msg.ShowAsync();
}
});
}
}

p is always null.

Any idea how i should do it?

Joost van Schaik said...

@poul, there are some sample solution associated with this article. Did you check them out? If you did and that did not help, can you send me a mini-solution to repro demonstrate your problem (putting questions marks on what you exactly don't understand) and then I will have a look at it.

Unknown said...

I checked em out. I came to the conclusion that its the commandparameter i havent bound correct.



I need to bind it to the selected item.

Then I also found out that there might be a few issues later also, as the frame.navigate is not accessible from the viewmodel.

So right now i just added an eventhandler in code behind.
ItemClick="WelcomeTileClickedHandler"

Anonymous said...

Hi Joost,

first thank you on this great library. It's a real saver :)

One quick question.

Is it possible with your lib to bind command in eventtocommand like

... Command={Binding SomeCommandToBindFromParent} ...

or we only can pass regular string to Command like

... Command="SomeCommand"...

I'm asking this because I have case when I need to bind command from parent datacontext and if we only can invoke commands without binding I'm afraid that I cannot do that. Or I'm not seeing something :)

Thank you in advance!

Joost van Schaik said...

@Renato, thanks for your compliments. Unfortunately the behavior does not support command binding right now. I think the behavior could be fairly easy adapted to do that, by changing Command to type ICommand, binding to that and that directly invoke the command in stead of doing the Reflection dance as I do now

Amit Raz said...

This does not work for me.

I keep getting null once on the following line:
var commandGetter = dcType.GetRuntimeMethod("get_" + Command, new Type[0]);

when I check by using GetRuntimeMethods I see that indeed there is no matching name...
Any advice?

Thanks

Amit

Joost van Schaik said...

@Amit you probably try to attach to an event that's not a RoutedEvent. That's the limitation of this approach.

Manvik said...

Hi Joost,

Thanks for the awesome library.
I have a Gridview and I want to get the ItemClicked as I want to tap the ItemClicked event and not the selection changed.

What is the possible solution for the same.

Joost van Schaik said...

@MK07 Sorry for the late response, have been quite busy for some time. I've added a sample project to http://dotnetbyexample.blogspot.nl/2012/11/a-winrt-behavior-to-mimic.html that I hope will show you how to do it. Scroll down to see sample code and a complete download link

Unknown said...

Hello,

I used your excellent EventToBoundCommandBehavior in 3 GridView in my Win8 app.

In one of them, I'm getting this error:

"A value of type 'EventToBoundCommandBehavior' cannot be added to a collection or dictionary of type 'BehaviorCollection'.
















Please note that in my 2 other GridView, the winRtBehaviors:Interaction.Behaviors is in the same PostPreviewControl.

Thanks!
ArchieCoder

Joost van Schaik said...

@Sébastien could you please send me a repro? I get a lot of questions and guessing what your code looks like before even trying to debug it is not very productive ;-)
And please refrain from entering a lot of empty lines in your comment :)

Xander Dumaine said...

I added the project via nuget, but when I add the xaml, I get the error "Interaction does not exist in the namespace WinRtBehaviors" and "EventToCommandBehavior does not exist in the namespace Win8nl_Behavior" and ideas?

Joost van Schaik said...

@Xander - what did you do exactly? You added win8nl or winrtbehaviors? You will need win8nl. Did you add the namespace at the top of your XAML file? If you did all this, can you send me a small repro showing your problem?

Unknown said...

Thank you for taking the time to produce and support a very useful library. I want to get at an events argument in my view model (TextBox - KeyDown event, which key was pressed). Is it possible to bind the command parameter to the event arguments? Thanks in advance.

Joost van Schaik said...

Rob, you might want to have a look at http://dotnetbyexample.blogspot.nl/2012/11/passing-eventargs-to-winrt.html. We have got you covered buddy! ;)

Pallam Madhukar said...

Is any thing wrong with CommandParameter, I am using like below:






Please tell me, i am always getting null, if i binded property from viewmodel its giving value but not with the listbox element

Joost van Schaik said...

@Pallam, I don't think your comment quite got trough the way you intendend

Joost van Schaik said...

@Sebastian (and others) - if you get the error ‘"A value of type 'EventToBoundCommandBehavior' cannot be added to a collection or dictionary of type 'BehaviorCollection' you probably have messed up references. Please make a backup from your project, remove all nuget packages from it, clear the bin directory, and then add win8nl as nuget package again. That should solve it. There is a chance your app.xaml gets messed up - hence the backup of that.

Unknown said...

If anyone is interested how to remove the dependency on Reactive framework, here's the solution:
http://stackoverflow.com/questions/16647198/how-to-dynamically-bind-event-to-command-in-winrt-without-reactive-framework