Home > C#, MVVM, WinRT > Windows 8.1 Behavior SDK: How to use InvokeAction with InputConverter to pass arguments to a Command

Windows 8.1 Behavior SDK: How to use InvokeAction with InputConverter to pass arguments to a Command

10/01/2014

The new Behavior SDK that comes with Windows 8.1 contains, among the others, an action called InvokeActionCommand that it is capable of invoking an ICommand on the ViewModel, so that it is useful in MVVM scenarios: used with the EventTriggerBehavior, it lets us attach commands to each event of every control.

For example, suppose we have the following Model and ViewModel (of MVVM Light):

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class MainViewModel : ViewModelBase
{
    public Person[] People
    {
        get
        {
            return new Person[]
            {
                new Person { FirstName = "Pippo", LastName="Goofy" },
                new Person { FirstName = "Mickey", LastName="Mouse" },
                new Person { FirstName="Donald", LastName="Duck" }
            };
        }
    }

    public RelayCommand<ItemClickEventArgs> ItemSelectedCommand { get; set; }

    public MainViewModel()
    {
        ItemSelectedCommand = new RelayCommand<ItemClickEventArgs>((args) =>
        {
            // ...
        });
    }
}

And the corresponding XAML Page:

<Page
    ...
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    DataContext="{Binding Source={StaticResource Locator}, 
      Path=MainViewModel}">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <GridView IsItemClickEnabled="True" ItemsSource="{Binding People}">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Width="200">
                        <TextBlock Text="{Binding FirstName}" Margin="0,0,3,0" />
                        <TextBlock Text="{Binding LastName}" />
                    </StackPanel>
                </DataTemplate>
            </GridView.ItemTemplate>
            <i:Interaction.Behaviors>
                <core:EventTriggerBehavior EventName="ItemClick">
                    <core:InvokeCommandAction Command="{Binding ItemSelectedCommand}" />
                </core:EventTriggerBehavior>
            </i:Interaction.Behaviors>
        </GridView>
    </Grid>
</Page>

As the name implies, the trigger defined at line 19 is raised when the ItemClick event is generated. In this case, the InvokeCommandAction (line 20) is executed, invoking the ItemSelectedCommand on the ViewModel.

Using this pattern, the EventArgs associated to the event is automatically passed as argument to the Command, so that we can read its ClickedItem property to access the item that has been clicked. However, this solution has some drawbacks, because we’re passing to the ViewModel an object that belongs to the Windows.UI.Xaml.Controls, and moreover we need to manually cast the ClickedItem property to the real object.

A better approach, from an MVVM perspective, would be to pass directly the real object to the Command. To do this, we need to use the InputConverter property of InvokeCommandAction. It allows to define an IValueConverter for the parameter the will be passed to the Execute method of the Command. So, we can define the following class:

public class ItemClickedConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var args = value as ItemClickEventArgs;

        if (args != null)
            return args.ClickedItem;

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
        string language)
    {
        throw new NotImplementedException();
    }
}

The value argument of the Convert method is the ItemClickEventArgs parameter of the ItemClick event. We cast it and then we return its ClickedItem property, that represents the real object that has been clicked.

To use this converter, we just need the following code:

<Page.Resources>
    <local:ItemClickedConverter x:Name="ItemClickedConverter" />
</Page.Resources>

...
<i:Interaction.Behaviors>
    <core:EventTriggerBehavior EventName="ItemClick">
        <core:InvokeCommandAction Command="{Binding ItemSelectedCommand}"
                                  InputConverter="{StaticResource ItemClickedConverter}" />
    </core:EventTriggerBehavior>
</i:Interaction.Behaviors>
...

In this way, before invoking the action, the ItemClickEventArgs is passed to the ItemClickConverter class, that extracts and returns the ClickedItem property. This object (i.e., the real Person class) is then passed to the ItemSelectedCommand, so we need to modify it accordingly:

public RelayCommand<Person> ItemSelectedCommand { get; set; }

public MainViewModel()
{
    ItemSelectedCommand = new RelayCommand<Person>((person) =>
    {
        // ...
    });
}

Like any other Converter, we have the possibility to pass an optional parameter and the language to the Convert method, using respectively the InputConverterParameter and InputConvertLanguage properties of the InvokeCommandAction object.

Categories: C#, MVVM, WinRT
  1. 10/01/2014 at 10:08

    Inspired by NavigateWithEventArgsToPageAction.cs of “Prism for the Windows Runtime”, I am using the following generic converter:

    public sealed class ArgsConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, string language)
    {
    if (value != null)
    {
    var propertyPath = parameter as string;
    if (!string.IsNullOrWhiteSpace(propertyPath))
    {
    //Walk the ParameterPath for nested properties.
    var propertyPathParts = propertyPath.Split(‘.’);
    object propertyValue = value;
    foreach (var propertyPathPart in propertyPathParts)
    {
    var propInfo = propertyValue.GetType().GetTypeInfo().GetDeclaredProperty(propertyPathPart);
    propertyValue = propInfo.GetValue(propertyValue);
    }
    return propertyValue;
    }
    }
    return value;
    }

    }

    • 10/01/2014 at 10:38

      Yes, you are correct. I have shown a particular scenario to better explain how the system works. Your solution can be adopt to generalize the approach.

  1. 10/01/2014 at 11:06
Comments are closed.
%d bloggers like this: