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 ;-)