Handling gestures in C# WinRT using a behaviour

We’re using the WinRTBehaviors as WinRT doesn’t have them built in.  Behaviours (with a u sorry, I am English) are great and I’m really pleased that someone has ported them across – one less thing for us to do – thanks!

So gestures are cool but not massively easy to add in.  It isn’t complicated but it’s repetitive event wire up stuff.  It would be nice to wire up a cross swipe to a command on the view model.  To facilitate this we created a behaviour to encapsulate the gesture event wire up and expose a command for each gesture we support (at present we only need cross swipe and tap).

Note:  The cross swipe gesture ends in a tap event so if you want to do one thing on tap and another on cross swipe then you’ll have to do all the event handling via the gesture otherwise you will always run the tap action when cross swiping.  This is why it is useful for us to encapsulate all gestures in a single behaviour.

Here’s the behaviour:

public class GestureBehavior : Behavior<FrameworkElement>
{
    GestureRecognizer _pageGesture;

    public static readonly DependencyProperty CrossSwipeCommandProperty =
        DependencyProperty.Register("CrossSwipeCommand", typeof(string), typeof(GestureBehavior), new PropertyMetadata(default(string)));

    public static readonly DependencyProperty TapCommandProperty =
        DependencyProperty.Register("TapCommand", typeof(string), typeof(GestureBehavior), new PropertyMetadata(default(string)));

    public string CrossSwipeCommand
    {
        get { return (string)GetValue(CrossSwipeCommandProperty); }
        set { SetValue(CrossSwipeCommandProperty, value); }
    }

    public string TapCommand
    {
        get { return (string)GetValue(TapCommandProperty); }
        set { SetValue(TapCommandProperty, value); }
    }

    protected override void OnAttached()
    {
        AssociatedObject.AddHandler(PointerPressedEvent, (PointerEventHandler)_OnPointerPressed, true);
        AssociatedObject.AddHandler(PointerReleasedEvent, (PointerEventHandler)_OnPointerReleased, true);
        AssociatedObject.AddHandler(PointerMovedEvent, (PointerEventHandler)_OnPointerMoved, true);
        AssociatedObject.AddHandler(RightTappedEvent, (RightTappedEventHandler)_OnRightTap, true);
        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        if (_pageGesture != null && _pageGesture.IsActive)
        {
            _CompleteGesture();
        }
        AssociatedObject.RemoveHandler(PointerPressedEvent, (PointerEventHandler)_OnPointerPressed);
        AssociatedObject.RemoveHandler(PointerReleasedEvent, (PointerEventHandler)_OnPointerReleased);
        AssociatedObject.RemoveHandler(PointerMovedEvent, (PointerEventHandler)_OnPointerMoved);
        AssociatedObject.RemoveHandler(RightTappedEvent, (RightTappedEventHandler)_OnRightTap);

        base.OnDetaching();
    }
    private void _OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        _pageGesture = new GestureRecognizer
        {
            CrossSlideHorizontally = true,
            ShowGestureFeedback = true,
            CrossSlideThresholds = new CrossSlideThresholds { RearrangeStart = 0, SelectionStart = 5, SpeedBumpStart = 0, SpeedBumpEnd = 0 },
            GestureSettings = GestureSettings.CrossSlide | GestureSettings.Tap
        };
        _pageGesture.CrossSliding += _PageGestureOnCrossSliding;
        _pageGesture.Tapped += _TappedButDidNotSwipe;
        _pageGesture.ProcessDownEvent(e.GetCurrentPoint(AssociatedObject));
    }

    private void _OnPointerMoved(object sender, PointerRoutedEventArgs e)
    {
        if (_pageGesture != null)
        {
            try
            {
                _pageGesture.ProcessMoveEvents(e.GetIntermediatePoints(AssociatedObject));
            }
            catch
            {
                _CompleteGesture();    
            }
        }
    }
        
    private void _OnPointerReleased(object sender, PointerRoutedEventArgs e)
    {
        if (_pageGesture != null)
        {
            try
            {
                _pageGesture.ProcessUpEvent(e.GetCurrentPoint(AssociatedObject));
            }
            catch
            {
            }
            finally 
            {
                _CompleteGesture();
            }
        }
    }
        
    private void _CompleteGesture()
    {
        _pageGesture.CompleteGesture();
        _pageGesture.CrossSliding -= _PageGestureOnCrossSliding;
        _pageGesture.Tapped -= _TappedButDidNotSwipe;
        _pageGesture = null;
    }

    private void _PageGestureOnCrossSliding(GestureRecognizer sender, CrossSlidingEventArgs args)
    {
        if (args.CrossSlidingState == CrossSlidingState.Completed && CrossSwipeCommand != null)
        {
            AssociatedObject.ExecuteCommand(CrossSwipeCommand);
        }
    }
                
    private void _TappedButDidNotSwipe(GestureRecognizer sender, TappedEventArgs args)
    {
        if (TapCommand != null)
        {
            AssociatedObject.ExecuteCommand(TapCommand);
        }
    }
        
    private void _OnRightTap(object sender, RightTappedRoutedEventArgs e)
    {
        if (CrossSwipeCommand != null)
        {
            AssociatedObject.ExecuteCommand(CrossSwipeCommand);
        }
    }
}

Here is how it’s used in XAML:

<ListView ... >
                <b:Interaction.Behaviors>
                    <i:GestureBehavior CrossSwipeCommand="DoSomething" TapCommand="DoSomethingElse" />
                </b:Interaction.Behaviors>
....

We have other behaviours which execute commands and we’ve encapsulated some of that command execution behaviour in the following code:

public static void ExecuteCommand(this FrameworkElement associatedObject, string commandName)
{
    if (associatedObject.DataContext == null) return;
    
    new CommandProxy(associatedObject.DataContext, commandName).Execute();
}

public class CommandProxy
{
    readonly string _commandName;
    readonly ICommand _command;

    public CommandProxy(object dataContext, string commandName)
    {
        _commandName = commandName;
        _command = _ExtractCommand(dataContext, commandName);
    }

    ICommand _ExtractCommand(object dataContext, string commandName)
    {
        var commandProperty = dataContext.GetType().GetRuntimeProperty(commandName);
        if (commandProperty == null)
        {
            return null;
        }

        return (ICommand)commandProperty.GetValue(dataContext);
    }

    public bool FoundCommand
    {
        get { return _command != null; }
    }

    public bool Execute()
    {
        if (!FoundCommand)
        {
            throw new NullReferenceException(string.Format("Cannot execute the command as no command named {0} could be found on the instance.", _commandName));
        }
        if (!_command.CanExecute(null))
        {
            return false;
        }
        _command.Execute(null);
        return true;
    }
}

I really should set up a git repo for all the WinRT stuff discussed in the blog – hopefully I’ll get some time once the project is done.

One thing we don’t like, but couldn’t find a way around, was handling exceptions when the GestureRecognizer fires events.  We were seeing odd behaviour when beginning a gesture on an empty list then populating the list.  The gesture wouldn’t complete and would fire more events but then throw exceptions (about not having same pointer id or different frame…).  With this here the app doesn’t crash and gestures still work ok.

Advertisements

About Tom Peplow

C# .Net developer based in London and the South Coast
This entry was posted in Uncategorized and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s