Iris Classon
Iris Classon - In Love with Code

Passing event arguments from XAML in Windows Store Apps (InputConverter, InputConverterParameter etc)

I learned something pretty cool a few days ago after a two day mission looking over all the different ways that you can work with the events provided by the control with a strict MVVM pattern. That is a longer blog post coming soon, talking about attached dependency properties, dependency properties, behaviors and attached behavior and more. I wanted to double check that I indeed had covered the topic well with reasonable good implementations, and since there is so much to XAML and its hard to know it all I relied on some valuable feedback from two friends of mine, Oren Novotny and Laurent Bugnion.

The first thing that came to mind when wanting to route events to commands as you usually do with MVVM and XAML was to either create a dependency property or use behaviors and pass the event args. Out of the two behaviors would be a better way to go, allowing us to keep the UI logic in the XAML and also not having to wire up unnecessary dependency properties. Save those for special occasions. The question was however, how to I pass event args with InvokeCommandAction when the attribute for passing event args is missing in Windows Store Apps. I could pass in a property, such as the QueryText of the SearchBox (see my last blog post on implementing Search in Windows Store Apps), but not the event args which would let us access the object that we append search suggestions to as the user is typing.

This is where Laurent told me something rather cool that I didn’t know about, an attribute called InpuConverter found on the InvokeCommandAction (Windows Store only I believe). Here you can provide a converter which will get the event args as its value. You can then grab whatever you need and pass it to the command. And as Laurent said, its best to not pass in the arguments object as this gives us an undesired direct interaction with the ViewModel from the View.

Here is the code sample for this blog post that I made, on MSDN

Download from my blog

Or grab it from GitHub

The converter is, if you haven’t made a converter before (if you haven’t then start using them, they are pretty handy!) you simply create a class that inherits from the IValueConverter, and implement the two convert methods that converts the object to and from.

InputConverter6

if you get this when running the sample:

InputConverter00

Then you forgot to add a reference to the Behavior SDK :)

InputConverter0

I’ve been unable to find any documentation on the new attributes, but they are rather straight forward to use once you know how to use them. The three attributes are (one the InvokeCommand):

InputConverter

This one gets or sets the converter used for the command. The command has to be of type ICommand, and the converter of the type IValueConverter.

InputConverterLanguage

Gets or sets the language that is passed into the converter as a string

InputConverterParameter

Sets or gets the parameter passed into the converter. Notice that its passed to the converter not the command. So use this one when you need to convert the parameter. Otherwise, just use the CommandParameter.

Here is an MVVM implementation where I’m using the converter to pass in the objects I need in my command. The Delegate command is generic so I can create commands that know what type to expect.

I’ve provided images as well, in case the code snippets are broken in the future :) Keen to know your thoughts!

Mainpage XAML

[sourcecode language=“XML”]

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">  
    <SearchBox SearchHistoryEnabled="False" x:Name="SearchBox" Width="500" Height="50">  
        <SearchBox.Resources>  
            <local:SearchArgsConverter x:Name="ArgsConverter"/>  
        </SearchBox.Resources>  
        <interactivity:Interaction.Behaviors>  
            <core:EventTriggerBehavior EventName="SuggestionsRequested">  
                <core:InvokeCommandAction   
                    Command="{Binding SuggestionRequest}"  
                    InputConverter="{StaticResource ArgsConverter}"  
                    InputConverterLanguage="en-US"  
                    InputConverterParameter="{Binding ElementName=SearchBox, Path=SearchHistoryEnabled}"/>  
            </core:EventTriggerBehavior>  
        </interactivity:Interaction.Behaviors>  
    </SearchBox>  
</Grid>  

Mainpage codebehind (pull this out to a viewmodel)

[sourcecode language=“csharp”]
using System;
using System.Collections.Generic;
using System.Linq;

namespace InputConverterExample
{
public sealed partial class MainPage
{
public DelegateCommand Search { get; set; }
public DelegateCommand SuggestionRequest { get; set; }

    public MainPage()  
    {  
        InitializeComponent();  
        Search = new DelegateCommand<string>(SearchedFor, o => true);  
        SuggestionRequest = new DelegateCommand<ISuggestionQuery>(SuggestionRequestFor, o => true);  

        DataContext = this;  
    }  

    private void SuggestionRequestFor(ISuggestionQuery query)  
    {  
        IEnumerable<string> filteredQuery = \_data  
            .Where(suggestion => suggestion.StartsWith(query.QueryText,  
                StringComparison.CurrentCultureIgnoreCase));  
        query.Request.SearchSuggestionCollection.AppendQuerySuggestions(filteredQuery);  
    }  

    private readonly string[] \_data = { "Banana", "Apple", "Meat", "Ham" };  

    private void SearchedFor(string queryText)  
    {  
    }  
}  

}

[/sourcecode]

converter

[sourcecode language=“csharp”]
using System;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;

namespace InputConverterExample
{
public sealed class SearchArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var args = (SearchBoxSuggestionsRequestedEventArgs)value;
var displayHistory = (bool)parameter;

        if (args == null) return value;  
        ISuggestionQuery item = new SuggestionQuery(args.Request, args.QueryText)  
        {  
            DisplayHistory = displayHistory  
        };  
        return item;  
    }  

    public object ConvertBack(object value, Type targetType, object parameter, string language)  
    {  
        return value;  
    }  
}  

}

[/sourcecode]

Data interface

[sourcecode language=“csharp”]
using Windows.ApplicationModel.Search;

namespace InputConverterExample
{
public interface ISuggestionQuery
{
SearchSuggestionsRequest Request { get; }
string QueryText { get; }
bool DisplayHistory { get; set; }
}
}

[/sourcecode]

Data model

[sourcecode language=“csharp”]
using Windows.ApplicationModel.Search;

namespace InputConverterExample
{

public class SuggestionQuery : ISuggestionQuery  
{  
    public SuggestionQuery(SearchSuggestionsRequest request, string queryText)  
    {  
        Request = request;  
        QueryText = queryText;  
    }  

    public SearchSuggestionsRequest Request { get; private set; }  
    public string QueryText { get; private set; }  
    public bool DisplayHistory { get; set; }  
}  

}

[/sourcecode]

delegatecommand

[sourcecode language=“csharp”]
using System;
using System.Windows.Input;

namespace InputConverterExample
{
public class DelegateCommand : ICommand
{
private readonly Predicate _canExecute;
private Action _handler { get; set; }
public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<T> handler, Predicate<object> canExecute)  
    {  
        this.\_handler = handler;  
        \_canExecute = canExecute;  
    }  

    public void RaiseCanExecuteChanged()  
    {  
        if (CanExecuteChanged != null)  
            CanExecuteChanged(this, EventArgs.Empty);  
    }  

    public bool CanExecute(object parameter)  
    {  
        return \_canExecute == null || \_canExecute(parameter);  
    }  

    public void Execute(object parameter)  
    {  
        \_handler((T)parameter);  
    }  
}  

[/sourcecode]

IMAGES :) Click for larger size

Comments

Leave a comment below, or by email.
Javier
3/2/2014 2:28:16 PM
Thank you very much for this post, im currently developing an app using mvvm light and i couldnt get any useful examples on how to manage events between the xaml and the viewmodel.
Kisses from Spain! 
Iris Classon
3/2/2014 10:07:10 PM
Reply to: Javier
Talking about MVVM light, it was actually Laurent (creator of MVVM light) that told me about these properties :D If you need help don't hesitate to contact me, or Laurent! I'm @IrisClasson on twitter and Laurent is @LBugnion 
Javier
3/2/2014 11:54:14 PM
Reply to: Iris Classon
I most certainly will! :) 


Last modified on 2013-12-10

comments powered by Disqus