Tuesday, June 18, 2013

Tap, Click, Command or Select - How to trigger navigation.

Here's a scenario: You have a number of items on a page and you want the person using the app to be able to touch one of those items and have the app navigate to another page.

It's a simple and common scenario. It's also one where there are lots of ways of implementing the
behaviour. But which one should be used?

The navigation part is pretty straight forward but how to detect the "touch" and intent.

There are four options available:
  1. Contain each item in a Button and use the `Click` event to detect the users action.
  2. Use the `Tap` event on the containing UIElement to detect the users action.
  3. Contain each item in a Button and bind to the `Command` to detect the users action.
  4. If the items are in a  ListBox (or similar) use the SelectionChanged event to determine the users action.
Some may think there is no difference or they're interchangeable but there are some important differences.

A Click event occurs when contact is made with the sceen and the same control is still under the touch point when contact is lost. This is regardless of how long the touch was made for or what happened in the mean time. This means you can move around the screen, move off of the control and back onto it, move the control within a scrollable area, and as long as there is no other control which will capture the gesture, swipe across the control and still trigger the click event. This may be more than you want or a user will expect to trigger the action/navigation.

A Tap event is intended to be used when you want to capture the users intention to interact with a screen element by tapping it. To trigger the tap event of a control contact must be made and released within a short period of time (about 300ms) and without the touchpoint moving more than a small amount.  This avoids the event being triggered by pressing on something for a long period of time or when moving a control on the screen. This is what you should be using instead of a `Click` event as it is designed and created to be used for finger based interaction while the click event was created for use with a mouse.

Using the SelectionChanged event for items in a ListBox (or similar) is a popular technique as it is used by the DataBound project template. There are a couple of issues with this though. The first is that you must clear the selection after it's made to allow the selection of the same item again. The second, and bigger issue in my opinion, is that this is a misuse of what the ability to select an item is for. The ability to select an item (or items) is to enable it to be distinguished from the other items in the group. That's why there's a selected state. It allows you to see which items have been selected, so you can act on them. Thirdly, triggering a change in the selected item can also happen accidentally when scrolling a list.
If you have multiple items in a list and want to be able to perform an action when one of them is tuched, it would be better to use a Tap event on the individual item, rather than an event attacehd to the outer collection.
Like this:

    <ListBox ItemsSource="{Binding ListOptions}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid Tap="ItemTapped">
                    <TextBlock Text="{Binding}" />
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>


But, I hear you say, what about MVVM? I don't want to use coded behind, I want to bind the action to an ICommand on the ViewModel.

Yes, the ICommand is the solution here and the ButtonBase control has a Command property that you can bind your ICOmmand implementation to. But how is it triggered?
Disappointingly, from my perspective, it is triggered by either a Click or Tap event. This means that it can still be triggered in the same unexpected and undesired ways as a Click event.

Fortunately it's easy to create a user control that has the ability to call an ICommand implementation when it is Tapped and not Clicked.

Here's the incredibly simple XAML you need:

<UserControl
    x:Class="TapClickCommand.TapCommandControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Tap="ControlTapped" />

Yes, it's just an empty control with a Tap event handler wired up.

Then we just need the code to make it work:
namespace TapClickCommand
{
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
 
    public partial class TapCommandControl : UserControl
    {
        public readonly DependencyProperty TapCommandProperty =
            DependencyProperty.Register(
                "TapCommand",
                typeof(ICommand),
                typeof(TapCommandControl),
                new PropertyMetadata(null));
 
        public readonly DependencyProperty TapCommandParameterProperty =
            DependencyProperty.Register(
                "TapCommandParameter",
                typeof(object),
                typeof(TapCommandControl),
                new PropertyMetadata(null));
 
        public TapCommandControl()
        {
            InitializeComponent();
        }
 
        public ICommand TapCommand
        {
            get { return (ICommand)GetValue(TapCommandProperty); }
            set { this.SetValue(TapCommandProperty, value); }
        }
 
        public object TapCommandParameter
        {
            get { return GetValue(TapCommandParameterProperty); }
            set { this.SetValue(TapCommandParameterProperty, value); }
        }
 
        public void ControlTapped(object sender, GestureEventArgs e)
        {
            if (this.TapCommand != null)
            {
                this.TapCommand.Execute(this.TapCommandParameter);
            }
        }
    }
}


You use it just like the Command property in ButtonBase.
e.g.

    <local:TapCommandControl TapCommand="{Binding TheTapCommand}"
                             TapCommandParameter="{Binding Id}">
        <TextBlock Style="{StaticResource PhoneTextLargeStyle}"
                    Text="{Binding Name}" />
    </local:TapCommandControl>

Voila!


So, to summarize:
Use a Tap event, rather than Click or SelectionChanged if using code behind.
Bind commands to be triggered by a Tap event rather than a Click event if using commanding.




7 comments:

  1. Pablo Alarcon3:40 pm

    I agree understanding the difference between Click and Tap can be important, but I would suggest using EventToCommand behaviour or a custom Behaviour/AttachedProperty to handle it instead of creating an UserControl for it. Cleaner Separation of Concerns and closely the same code needed

    ReplyDelete
  2. Hi Pablo.

    Yes EventToCommand is great (I should have included that above) but I find it a bit long winded. In that I invariably end up creating user controls to encapsulate UI functionality (even if a generic Tiltable content control) adding a TapCommand is no real effort. My big point was not to use a Click event or the default Command that is in ButtonBase.

    ReplyDelete
  3. MVVM Light has all you need to just do:





    ReplyDelete
  4. Hi Cristóvão,
    "all you need to just do" what?

    ReplyDelete
  5. Hi Matt,
    Trying to use tap on the grid, and I'm finding it interferes with selection. Full details here:
    http://stackoverflow.com/questions/17210545/how-can-i-reliably-change-selecteditem-on-wp7-listbox-without-firing-tap

    ReplyDelete
  6. Kinnara7:15 am

    The OS behavior is similar to click.

    ReplyDelete
  7. Hey great post. Exactly what I was looking for !

    ReplyDelete

I get a lot of comment spam :( - moderation may take a while.