14 November 2012

A WinRT behavior to mimic EventToCommand (take 2)

(Updated with sample code January 19 2013)

Some time ago I posted a WinRT behavior to mimic EventToCommand. This behavior cunningly looked for the command name in the DataContext, but it occurred to me (and some blog post commenters as well) that it actually might be more logical skip all that skullduggery and let the developer bind to a command in stead of letting her/him provide its name as string and then go look for it.

Anyway, not wanting to break compatibility I added a new behavior to my win8nl library on CodePlex, very originally called EventToBoundCommandBehavior.

It’s working is very similar to the original EventToCommandBehavior. There are two notable differences:

  • The Command property is now no longer of type string but ICommand
  • The FireCommand method is now very simple:
private void FireCommand()
{
  if (Command != null && Command.CanExecute(CommandParameter))
  {
    Command.Execute(CommandParameter);
  }
}

Which goes to show that you can be too clever.

If you want to replace the original behavior for the new one – say, you used it like this

<TextBlock Text="TextBlock" FontSize="48">
  <WinRtBehaviors:Interaction.Behaviors>
    <Behaviors:EventToCommandBehavior Event="Tapped" 
      Command="TestCommand" 
      CommandParameter="{Binding TestProperty, Mode=TwoWay}"/>
  </WinRtBehaviors:Interaction.Behaviors>
</TextBlock>

Just go to the places marked red/underlined:

<TextBlock Text="TextBlock" FontSize="48">
  <WinRtBehaviors:Interaction.Behaviors>
    <Behaviors:EventToBoundCommandBehavior Event="Tapped" 
      Command="{Binding TestCommand}" 
      CommandParameter="{Binding TestProperty, Mode=TwoWay}"/>
  </WinRtBehaviors:Interaction.Behaviors>
</TextBlock>

Those who want to see the full source code of the new (and largely simplified) behavior can do so at http://win8nl.codeplex.com/SourceControl/changeset/view/20896#395786

Update: I’ve added some more sample code as a lot of people seem to struggle with the correct use of this behavior. Lots of time I get program problems like this: “I have a list in my view model and a command yet my command is not fired when then I tap the item”. The view model usually contains these properties:

public ObservableCollection Phones { get; set; }

public ICommand TappedCommand
{
  // code omitted
}

and the XAML is like this:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}" 
  DataContext="{StaticResource DemoViewModel}">

  <GridView ItemsSource="{Binding Phones}" SelectionMode="None" 
     IsTapEnabled="True">
    <GridView.ItemTemplate>
      <DataTemplate>
        <Grid Width="350">
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
          </Grid.ColumnDefinitions>
          <WinRtBehaviors:Interaction.Behaviors>
            <Behaviors:EventToBoundCommandBehavior 
              Command="{Binding TappedCommand}" 
              CommandParameter="{Binding}" Event="Tapped" />
          </WinRtBehaviors:Interaction.Behaviors>

          <TextBlock Text="{Binding Brand}"/>
          <TextBlock Text="{Binding Type}" Grid.Column="1"/>
        </Grid>
      </DataTemplate>
    </GridView.ItemTemplate>
  </GridView>
</Grid>

I this case, the programmer has lost track of what’s the exact data context. Happens all the time, even to me. I’ve used colors to depict the data context changes.

  • In the black code, DemoViewModel is the data context
  • In the red code, the ObserservableCollection Phones is the data context
  • In the blue code, a single Phone is the context.

So what this code tries to do is to call a command TappedCommand on class Phone in stead of DemoViewModel.So either the command needs to be in “Phone”, or the behavior’s binding must be updated like this:

<WinRtBehaviors:Interaction.Behaviors>
  <Behaviors:EventToBoundCommandBehavior 
    Command="{Binding TappedCommand, Source={StaticResource DemoViewModel}}" 
    CommandParameter="{Binding}" Event="Tapped" />
</WinRtBehaviors:Interaction.Behaviors>

Full working example can be found here.

9 comments:

Siimo Raba said...

Hi,

First of all: nice work!

I'm fairly new to WinRT & MVVM, so I hope this won't sound too dumb.

Basically, I want to load some data on page load. Since I've already got a command (RelayCommand) that does the work, I thought it'd be great if I could just fire that command on Loaded event of the page.

So, I wrote:




Other events, such as Tapped, GotFocus worked, but not Loaded. What may be the problem?

Thanks!
Sim

Joost van Schaik said...

@Simo, the behavior only attaches to events of type RoutedEvent. It may just be that Loaded is not a RoutedEvent. I need to check that.

Siimo Raba said...

Hi again, I solved my problem. Solution was a LOT simpler than I would've thought.

For some reason I had presumed the only way to get my data is by using the refresh command I'd created before. But all I had to do is call the method doing all the work for the command, in the ViewModel constructor and done.

So in this case I didn't need event to command functionality at all. Just thought I'd post this in case someone else is also overthinking MVVM when they're getting started.

Thanks

DrZoidberg said...

Hi, your methods works perfectly to fire a command behind a button in a normal view, but when i put a button in the AppBar and create command, the whole application crashes when i open the AppBar, before even clicking on a button.
I'm using MVVM light and the ICommand.

thx

Joost van Schaik said...

@DrZoidberg to bind a command to a button you don't need this behavior at all - just bind directly to the button's "Command" property. If you try to subscribe to another event, kindly provide a repro sample that I can debug and I will have a look at it.

Jürgen Gutsch said...

@Joost:
the Loaded event is a RoutedEvent:
public event RoutedEventHandler Loaded;

The event itself is not the problem:
I copied the sources of the EventToBoundCommandBehavior to debug and see what happened with the Loaded event.
The problem is in this special case is the bound command is always null. I don't know why.
It seems the viewmodel is not bound to the page if the Loaded event is fired. I don't know why because the DataContext is bound directly in the page via a viewmodel locator:
DataContext="{Binding Main, Source={StaticResource Locator}}"

Chears,
Juergen

Joost van Schaik said...

@Jürgen,

Do you have a repro solution? I can try to guess what you are doing wrong, but looking at your actual code will probably more useful

Alex Schlögl said...

@DrZoidberg I got the same issue. Did someone already got a solution for that? I don't want to use the "normal" events in the View.cs to handle when my app bar shows or hides.

Thx,
Alex

DrZoidberg said...

@Alex Schlögl, Try putting your AppBar inside the main grid, or make your appbar inside a usercontrol
here is a solution.

http://stackoverflow.com/questions/14320377/mvvm-windows-8-appbar-mvvm-event/14980554#14980554