Using the EventArgsConverter in MVVM Light, and why is there no EventToCommand in the Windows 8.1 version?

.NET, Blend, MVVM, Silverlight, Technical stuff, Windows 8, Windows Phone, Work, WPF
9 Comments

If you installed the latest version of MVVM Light, you might have noticed that EventToCommand is still missing from the Windows 8.1 DLLs. Why is that?

Historically, Behaviors have been added only in Windows 8.1 (they are not available in Windows 8.0 at all). Some of us developed workarounds, such as the attached behavior I published in May with my MSDN article about commands.

During that time, I have been in constant touch with the Blend team and the Windows 8 team to talk about the possibility to have behaviors in Windows 8. Very fast it became apparent that this could only be done with a few changes to the underlying implementation of Windows, and thus that it would be a Win8.1 only story. I was lucky to get invited to an early SDR (software design review) of the new Windows 8.1 Behavior implementation and to give feedback. Notably, I noticed that the InvokeCommandAction behavior was still missing a way to get the EventArgs down to the ViewModel, and asked the team if they could add this.

Getting the EventArgs to the ViewModel

Sometimes when you execute a command, it is nice to have access to the EventArgs inside the ViewModel directly, so that we can have additional information about the event. For example in the case of the mouse event, you sometimes want to know where the user clicked in order to perform different actions. In MVVM Light, we have the PassEventArgsToCommand property that can be set to true. If that is the case, the EventArgs will be passed down to the ICommand in the form of the CommandParameter argument. I made a quick sample to show that (check the “EventArgsWithoutConversion” project).

Converting the EventArgs

Of course getting EventArgs to a ViewModel breaks the important principle of separation between ViewModel and View, because EventArgs are often quite view-specific. If your ViewModel is in a separate assembly, you have to add references to view-only components, which is messy. This is why I added a converter for these situations. The converter must implement IEventArgsConverter, which is a very simple interface and quite similar in its intent to the IValueConverter that we all know from XAML bindings. The major difference between both is that I removed the ConvertBack method, which is unneeded in the case of an EventArgs conversion.

When you set the EventArgsConverter property of the EventToCommand in XAML, the converter’s Convert method will be called and should return a value which is convenient for a ViewModel. For instance if you get a MouseButtonEventArgs in the converter, you may want to know what is the location of the click. To do that, you have an additional property named EventArgsConverterParameter which is a Dependency Property. Setting this in XAML is easy through a binding, and then the Converter can convert this to a Point object which can be used in the ViewModel.

Note: The code sample is available as fully executable WPF4.5 and WinStore 8.1 applications.

XAML

<Window.Resources>
    <ResourceDictionary>
        <helpers:MouseButtonEventArgsToPointConverter x:Key="MouseButtonEventArgsToPointConverter" />
    </ResourceDictionary>
</Window.Resources>

<Grid x:Name="LayoutRoot"
      Background="#FF760000">

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseLeftButtonDown">
            <mvvm:EventToCommand 
                Command="{Binding ShowPositionCommand, Mode=OneWay}"
                EventArgsConverter="{StaticResource MouseButtonEventArgsToPointConverter}"
                EventArgsConverterParameter="{Binding ElementName=LayoutRoot}"
                PassEventArgsToCommand="True" />
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <TextBlock HorizontalAlignment="Center"
               Text="{Binding LastPosition}"
               VerticalAlignment="Center"
               Foreground="White"
               FontFamily="Segoe UI Light"
               FontSize="72"
               IsHitTestVisible="False" />
</Grid>

Converter

public class MouseButtonEventArgsToPointConverter : IEventArgsConverter
{
    public object Convert(object value, object parameter)
    {
        var args = (MouseButtonEventArgs)value;
        var element = (FrameworkElement)parameter;

        var point = args.GetPosition(element);
        return point;
    }
}

ViewModel

public class MainViewModel : ViewModelBase
{
    public const string LastPositionPropertyName = "LastPosition";

    private string _lastPosition = "Click somewhere";
    private RelayCommand<Point> _showPositionCommand;

    public string LastPosition
    {
        get
        {
            return _lastPosition;
        }
        set
        {
            Set(() => LastPosition, ref _lastPosition, value);
        }
    }

    public RelayCommand<Point> ShowPositionCommand
    {
        get
        {
            return _showPositionCommand
                    ?? (_showPositionCommand = new RelayCommand<Point>(
                        point =>
                        {
                            LastPosition = string.Format("{0:N1}, {1:N1}", point.X, point.Y);
                        }));
        }
    }
}

What about Windows 8.1?

Like mentioned above, there is no EventToCommand in the Windows 8.1 version of MVVM Light, and there will never be. This is because the Blend team listened to my feedback and added the same capability to their own InvokeCommandAction. There are a few small differences though. Notably, the team decided to go with a standard IValueConverter instead of the custom IEventArgsConverter interface. I understand their decision, even though I am always annoyed when I have an empty ConvertBack method in my IValueConverter implementations. But on the other hand, having an additional interface for this might be considered overkill, and I can live with their decision.

So in Windows 8.1 the code shown above becomes the following. Note that the ViewModel code is exactly the same as in WPF, so the differences are really only in the View layer as it should be.

Note: The code sample is available as fully executable WPF4.5 and WinStore 8.1 applications.

XAML

<Page.Resources>
    <helpers:MouseButtonEventArgsToPointConverter 
        x:Key="MouseButtonEventArgsToPointConverter" />
</Page.Resources>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
      x:Name="LayoutRoot">

    <interactivity:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="PointerPressed">
            <core:InvokeCommandAction 
                Command="{Binding ShowPositionCommand, Mode=OneWay}"
                InputConverter="{StaticResource MouseButtonEventArgsToPointConverter}"
                InputConverterParameter="{Binding ElementName=LayoutRoot}" />
        </core:EventTriggerBehavior>
    </interactivity:Interaction.Behaviors>

    <TextBlock HorizontalAlignment="Center"
               Text="{Binding LastPosition}"
               VerticalAlignment="Center"
               FontFamily="Segoe UI Light"
               FontSize="72"
               FontWeight="Light"
               IsHitTestVisible="False" />
</Grid>

Converter

public class MouseButtonEventArgsToPointConverter : IValueConverter
{
    public object Convert(
        object value, 
        Type targetType, 
        object parameter, 
        string language)
    {
        var args = (PointerRoutedEventArgs)value;
        var element = (FrameworkElement)parameter;

        var point = args.GetCurrentPoint(element);
        return new Point(point.Position.X, point.Position.Y);
    }

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

ViewModel: Same code as in WPF

What about other XAML frameworks?

Because the behaviors in Windows 8.1 are a new implementation, the changes to InvokeCommandAction won’t be backported to other XAML frameworks (WPF, Silverlight, Windows Phone…) at least not that I know of. In these XAML frameworks, you can still use the EventToCommand implementation shown above.

Poll: Is there value in adding an EventToCommand in Windows 8.1?

In the example above, the ViewModel code is exactly the same. The XAML and converter code are different though. Typically I recommend sharing viewmodel code but not view code between different XAML frameworks. However in certain cases, it might be interesting to share XAML code too. So my question to you is: Do you see a value in having an EventToCommand  in Windows 8.1? This component would take allow having exactly the same XAML code in all frameworks, and for instance allow to share XAML code between Windows Phone and Windows 8.1.

Such a wrapper would merely reproduce the InvokeCommandAction’s behavior but it would do so with a compatible XAML markup. Please comment below and let me know if you think it is needed to have EventToCommand in Windows 8.1.

Happy coding
Laurent

GalaSoft Laurent Bugnion
Laurent Bugnion (GalaSoft)

Share on Facebook
 

9 Responses to “Using the EventArgsConverter in MVVM Light, and why is there no EventToCommand in the Windows 8.1 version?”

  1. Thomas Mutzl Says:

    My vote: Nope.
    Having differences in the XAML is fine.

  2. Laurent Kempé Says:

    I don’t see the need neither

  3. Using the EventArgsConverter in MVVM Light, and why is there no EventToCommand in the Windows 8.1 version? Says:

    […] /* 0) { ratingctl00_cphMiddle_cphContent_tr3w45fwwvre_itemRating = result['Rating']; SetCurrentRating('ctl00_cphMiddle_cphContent_tr3w45fwwvre_itemRating_pnlHolder', result['Rating'], "disabled fullStar", "disabled emptyStar", true); if(votesCountctl00_cphMiddle_cphContent_tr3w45fwwvre_itemRating!=null) { votesCountctl00_cphMiddle_cphContent_tr3w45fwwvre_itemRating ++; SetVotesCount('ctl00_cphMiddle_cphContent_tr3w45fwwvre_itemRating_lblUsersRated', '(' + votesCountctl00_cphMiddle_cphContent_tr3w45fwwvre_itemRating + ' votes)'); } SetRatingCookie('r', 'i35046', '1'); } else if (result['Status'] == 1) { alert('The session has expired. Please refresh the page to be able to vote!'); } } /* ]]> */ (0 votes) 0 comments   /   posted by Silverlight Show on Jan 15, 2014 Tags:   windows-8.1 , win8dev , mvvm , laurent-bugnion Read original post by Laurent Bugnion at GalaSoft […]

  4. Windows App Developer Links – 2014-01-27 | Dan Rigby Says:

    […] Using the EventArgsConverter in MVVM Light, & why is there no EventToCommand in the Windows 8.1… (Laurent Bugnion) […]

  5. Arun Says:

    Hi, Is it possible to provide a sample on how to replace the EventToCommand for a button click and handle the event in viewmodel?

  6. Using InvokeCommandAction in Winodws 8.1 Universal Applications | Earn $20k Mo. 714-388-6147 Says:

    […] along w/ the MVVM Light framework, things have changed and it is ok. In fact Laurent has a nice post on how to use the new built-in EventTriggerBehior rather than the EventToCommand […]

  7. lbugnion Says:

    Hi,

    In windows 8.1, Use InvokeCommandAction. However you wouldn’t use EventToCommand for button click, just use the button’s Command property.

  8. Louis Says:

    Bonjour,

    j’ai commence a utiliser MVVM Light depuis 1 an (VS2012), c’est vraiment un tres bon framework.
    Pour une nouvelle appli (VS2013 Universal) quel est le meilleur moyen pour passer le parametre a un autre viewmodel (et autre page)?
    Si j’utilise

    j’ai le parametre avec e.Params dans le code behind mais pas mon view model.

    Pour l’avoir dans le view model il faut mieux utiliser un InvokeCommandAction en plus pour envoyer un message a mon viewmodel?
    C’est a dire avoir
    <Button……

    Ou cela n’est pas une bonne idee?

    Merci beaucoup

    Louis

  9. Louis Says:

    Bonjour,

    j’ai commence a utiliser MVVM Light depuis 1 an (VS2012), c’est vraiment un tres bon framework.
    Pour une nouvelle appli (VS2013 Universal) quel est le meilleur moyen pour passer le parametre a un autre viewmodel (et autre page)?
    Si j’utilise

    j’ai le parametre avec e.Params dans le code behind mais pas mon view model.

    Pour l’avoir dans le view model il faut mieux utiliser un InvokeCommandAction en plus pour envoyer un message a mon viewmodel?
    C’est a dire avoir
    Button……
    Interactivity:Interaction.Behaviors>
    Core:EventTriggerBehavior EventName=”Click”>

    /Core:EventTriggerBehavior>
    /Interactivity:Interaction.Behaviors>
    /Button>

    Ou cela n’est pas une bonne idee?

    Merci beaucoup

    Louis

Leave a Reply