Wednesday, 30 October 2013

Using Blend behaviours in a Windows Store app – update 1

Previously I wrote about replacing an attached behaviour that was used to execute a view model action, with a Blend behavior that executes a view model command. The advantage of this approach is that you use what’s provided by the Blend SDK, rather than having to write your own attached behaviour, which may introduce errors, and associated unit tests.

Following a conversation with a colleague, I realised that while the app works, my use of the InvokeCommandAction was incorrect. This blog post will correct this.

Implementation

A Blend behaviour and a Blend action are used to execute a view model command when the ItemClick event fires on the GridView.

<GridView Grid.Row="1"
          IsItemClickEnabled="True"
          ItemsSource="{Binding Photos}"
          Margin="140,0,0,0"
          SelectionMode="None">
    <interactivity:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="ItemClick">
            <core:InvokeCommandAction Command="{Binding PhotoNavigationCommand}" />
        </core:EventTriggerBehavior>
    </interactivity:Interaction.Behaviors>
    ...
</GridView>

An EventTriggerBehavior is used which handles the ItemClick event. The InvokeCommandAction specifies the view model command that will be invoked when the event fires. Please note that it is not necessary to specify a CommandParameter to pass to the view model command. This is because the EventTriggerBehavior passes the associated EventArgs to the embedded action. The overall effect is that when the ItemClick event fires on the GridView, the PhotoNavigationCommand in the MainPageViewModel class is invoked, with the PhotoNavigationCommand receiving the ItemClickEventArgs of the GridView as a parameter.

public DelegateCommand<object> PhotoNavigationCommand { get; private set; }
 
public MainPageViewModel(IRepository repository, INavigationService navigationService)
{
    _repository = repository;
    _navigationService = navigationService;
    PhotoNavigationCommand = new DelegateCommand<object>(NavigateToPhoto);
}
 
private void NavigateToPhoto(object parameter)
{
    var photo = ((ItemClickEventArgs)parameter).ClickedItem as FileInformation;
    if (photo != null)
    {
        _navigationService.Navigate("Photo", photo.Path);
    }
}

In the MainPageViewModel class the PhotoNavigationCommand is a generic DelegateCommand of type object. This is an attempt to eliminate the dependency on ItemClickEventArgs in the view model. In the MainPageViewModel constructor the PhotoNavigationCommand is initialiased to a new DelegateCommand that will execute the NavigateToPhoto method. This method gets the photo that was clicked on and uses the NavigationService to navigate to the PhotoPage where the clicked on photo will be displayed. However, the problem remains that the MainPageViewModel class still has one final dependency on ItemClickEventArgs, which is a UI specific concept. From a purist point of view for MVVM, this is unacceptable.

Summary

In this blog post I’ve updated my use of InvokeCommandAction to execute a view model command. There’s still a problem in that the MainPageViewModel class has a dependency on the ItemClickEventArgs type, which is a UI specific concept. From a purist point of view for MVVM, this is unacceptable. I’ll address this issue in a future blog post.

The sample app can be downloaded here.

4 comments:

  1. Hi,

    is there a way to avoid that CommandParameter is overridden by ItemClickEventArgs?

    ReplyDelete
    Replies
    1. You can specify a CommandParameter (e.g. CommandParameter="Option1") and your generic DelegateCommand will receive the string parameter instead of the ItemClickEventArgs.

      However, technically the CommandParameter isn't receiving the ItemClickEventArgs. The Action itself is receiving them from the EventTriggerBehavior. An Action method signature takes the form:

      object IAction.Execute(object sender, object parameter)
      {
      ...
      }

      Here, the sender will be the control (the GridView) with the parameter receiving the event args (in this case ItemClickEventArgs). Because a generic version of the DelegateCommand is used the ItemClickEventArgs are then passed on from the Action to the generic DelegateCommand.

      I'll go into this more in my next blog post.

      Delete
  2. You can get rid of ItemClickEventArgs via reflection:

    var type = parameter.GetType();
    PropertyInfo propInfo = type.GetRuntimeProperty("ClickedItem");
    var photo = (FileInformation)propInfo.GetValue(parameter);

    ReplyDelete
  3. Agreed. That's the approach I took when I modified all this in a further blog post - http://www.davidbritch.com/2013/11/writing-custom-blend-interaction-to.html

    ReplyDelete