Creating a custom gesture
This article demonstrates how to implement a custom gesture and register it in TouchManager. The following example will guide you through the creation of a two fingers tap gesture.
Implementing the gesture recognizer
To begin creating the gesture recognizer, you must first define a class that derives from GestureRecognizerBase and override its abstract methods.
Example 1: Defining the gesture recognizer
public class TwoFingerTapGestureRecognizer : GestureRecognizerBase
{
public TwoFingerTapGestureRecognizer(UIElement element)
: base(element)
{
}
public override void OnTouchEnter(GestureRecognizerEventArgs args)
{
}
public override void OnTouchDown(GestureRecognizerEventArgs args)
{
// code here
}
public override void OnTouchMove(GestureRecognizerEventArgs args)
{
}
public override void OnTouchUp(GestureRecognizerEventArgs args)
{
// code here
}
public override void OnTouchLeave(GestureRecognizerEventArgs args)
{
// code here
}
}
Public Class TwoFingerTapGestureRecognizer
Inherits GestureRecognizerBase
Public Sub New(element As UIElement)
MyBase.New(element)
End Sub
Public Overrides Sub OnTouchEnter(args As GestureRecognizerEventArgs)
End Sub
Public Overrides Sub OnTouchDown(args As GestureRecognizerEventArgs)
' code here '
End Sub
Public Overrides Sub OnTouchMove(args As GestureRecognizerEventArgs)
End Sub
Public Overrides Sub OnTouchUp(args As GestureRecognizerEventArgs)
' code here '
End Sub
Public Overrides Sub OnTouchLeave(args As GestureRecognizerEventArgs)
' code here '
End Sub
End Class
For this example, we will use only three of the gesture recognizer's methods: OnTouchDown, OnTouchUp and OnTouchLeave.
The example will use several helper fields:
- AllowedInterval: A readonly field of type TimeSpan that contains the allowed timeframe in which the gesture can be executed. If the time between the touch down and touch up is more than the allowed timespan, the gesture won't be executed.
- MaxTouchCount: A readonly field of type Int32 that defines the count of the required touches for executing the gesture.
- originTouchTime: A field of type DateTime that holds the time when the first finger was down.
-
isTwoFingerGesture: A field of type Boolean that tells if two fingers are on the screen and if the gesture can be executed.
-
touchIds: A field of type List
that holds the touch ids. The list is used to store the ids of the touches on the screen. -
handlers: A field of type List
that holds references to the event handlers attached to the recognizer.
You can find the code definitions of the helper fields in the following code snippet:
Example 2: Defining the fields of the recognizer
private static readonly TimeSpan AllowedInterval = TimeSpan.FromMilliseconds(300);
private static readonly int MaxTouchCount = 2;
private DateTime originTouchTime;
private bool isTwoFingerGesture;
private List<int> touchIds = new List<int>();
private List<WeakReference> handlers = new List<WeakReference>();
Private Shared ReadOnly AllowedInterval As TimeSpan = TimeSpan.FromMilliseconds(2000)
Private Shared ReadOnly MaxTouchCount As Integer = 2
Private originTouchTime As DateTime
Private isTwoFingerGesture As Boolean
Private touchIds As New List(Of Integer)()
Private handlers As New List(Of WeakReference)()
When we add the properties we can use them in the overrides of the gesture recognizer's methods.
-
OnTouchDown: When this method is called, we can store the touch id in the touchIds collection. If this is the first touch, we are setting the originTouchTime to the current time. If this is the second touch, we set the isTwoFingerGesture property to true.
Example 3: Implementing logic in the OnTouchDown method
public override void OnTouchDown(GestureRecognizerEventArgs args) { this.touchIds.Add(args.TouchId); if (this.touchIds.Count == 1) { this.originTouchTime = DateTime.Now; } this.isTwoFingerGesture = this.touchIds.Count == MaxTouchCount; }
Public Overrides Sub OnTouchDown(args As GestureRecognizerEventArgs) Me.touchIds.Add(args.TouchId) If Me.touchIds.Count = 1 Then Me.originTouchTime = DateTime.Now End If Me.isTwoFingerGesture = Me.touchIds.Count = MaxTouchCount End Sub
-
OnTouchLeave: When this method is called, we can remove the touch id of the leaving touch from the touchIds collection.
Example 4: Implementing logic in the OnTouchLeave method
public override void OnTouchLeave(GestureRecognizerEventArgs args) { this.touchIds.Remove(args.TouchId); }
Public Overrides Sub OnTouchLeave(args As GestureRecognizerEventArgs) Me.touchIds.Remove(args.TouchId) End Sub
-
OnTouchUp: When this method is called, we can remove the touch id of the leaving touch from the collection. Here we can also check if the gesture can be executed.
Example 5: Implementing logic in the OnTouchUp method
public override void OnTouchUp(GestureRecognizerEventArgs args) { this.touchIds.Remove(args.TouchId); if (this.touchIds.Count == 0 && this.isTwoFingerGesture) { var ellapsed = DateTime.Now - this.originTouchTime; if (ellapsed <= TwoFingerTapGestureRecognizer.AllowedInterval && GestureManager.CanActivateGesture(this.Element, "TwoFingerTapGesture")) { var token = GestureManager.ActivateGesture(this.Element, "TwoFingerTapGesture", null); GestureExtensions.RaiseTwoFingerTap(this.Element); token.DeactivateGesture(); } } }
Public Overrides Sub OnTouchUp(args As GestureRecognizerEventArgs) Me.touchIds.Remove(args.TouchId) If Me.touchIds.Count = 0 AndAlso Me.isTwoFingerGesture Then Dim ellapsed = DateTime.Now - Me.originTouchTime If ellapsed <= TwoFingerTapGestureRecognizer.AllowedInterval AndAlso GestureManager.CanActivateGesture(Me.Element, "TwoFingerTapGesture") Then Dim token = GestureManager.ActivateGesture(Me.Element, "TwoFingerTapGesture", Nothing) GestureExtensions.RaiseTwoFingerTap(Me.Element) token.DeactivateGesture() End If End If End Sub
Note that we are using the CanActivateGesture() and ActivateGesture() methods of the GestureManager class. This way we ensure that the gesture will be activated only if there is no collision between this gesture and the one that is currently active (if any).
The GestureExtensions is a class that holds a custom routed event and fires it when the gesture is executed. We will discuss its implementation in the Using the gesture section of this article.
Example 6 is the main logic of the recognizer. In this example you'll see how to implement logic for storing the event handlers for the gesture. We can do this through a couple of public methods that accept event handlers as arguments and add/remove them in the handlers collection defined earlier.
Example 6: Implementing logic for associating event handlers with the recognizer
public void AddHandler(RoutedEventHandler handler)
{
this.handlers.Add(new WeakReference(handler));
this.HasGestureHandlers = true;
}
public void RemoveHandler(RoutedEventHandler handler)
{
for (int i = this.handlers.Count - 1; i >= 0; i--)
{
var target = this.handlers[i].Target as RoutedEventHandler;
if (handler == target || target == null)
{
this.handlers.RemoveAt(i);
}
}
this.HasGestureHandlers = this.handlers.Count > 0;
}
Public Sub AddHandler(handler As RoutedEventHandler)
Me.handlers.Add(New WeakReference(handler))
Me.HasGestureHandlers = True
End Sub
Public Sub RemoveHandler
For i As Integer = Me.handlers.Count - 1 To 0 Step -1
Dim target = TryCast(Me.handlers(i).Target, RoutedEventHandler)
If handler = target OrElse target Is Nothing Then
Me.handlers.RemoveAt(i)
End If
Next
Me.HasGestureHandlers = Me.handlers.Count > 0
End Sub
Implementing the gesture recognizer factory
To add the gesture to a specific UIElement we need to create a recognizer factory that creates our gesture. The factory is a class that implements the IGestureRecognizerFactory interface.
Example 7: Defining the gesture recognizer factory
public class TwoFingerTapGestureRecognizerFactory : IGestureRecognizerFactory
{
public GestureRecognizerBase CreateGestureRecognizer(System.Windows.UIElement element)
{
return new TwoFingerTapGestureRecognizer(element);
}
public int Priority
{
get { return 0; }
}
public void RegisterGestureTransitions()
{
}
}
Public Class TwoFingerTapGestureRecognizerFactory
Implements IGestureRecognizerFactory
Public Function CreateGestureRecognizer(element As System.Windows.UIElement) As GestureRecognizerBase
Return New TwoFingerTapGestureRecognizer(element)
End Function
Public ReadOnly Property Priority() As Integer
Get
Return 0
End Get
End Property
Public Sub RegisterGestureTransitions()
End Sub
End Class
Using the gesture
Lets start with defining the UIElement on which the gesture will be performed.
Example 8: Defining the UI of the example
<UserControl x:Class="WpfApplication1.Example"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<TextBlock Text="Tap using two fingers to change the opacity of the rectangle" TextAlignment="Center"/>
<Rectangle x:Name="element" Width="200" Height="200" Fill="Orange" Margin="30"/>
</StackPanel>
</UserControl>
To use the gesture we will need to register the gesture recognizer factory and create a recogonizer for the UIElement that should listen for it. Then we can add an event handler for the UIElement.
Example 9: Creating a new instance of the recognizer and associating it with a UIElement
GestureManager.RegisterGestureRecognizerFactory(new TwoFingerTapGestureRecognizerFactory());
//-----------
TwoFingerTapGestureRecognizer recognizer = GestureManager.GetOrCreateGestureRecognizer<TwoFingerTapGestureRecognizer>(uiElement);
recognizer.AddHandler(handler);
element.AddHandler(TwoFingerTapEvent, handler, false);
GestureManager.RegisterGestureRecognizerFactory(New TwoFingerTapGestureRecognizerFactory())
'-----------'
Dim recognizer As TwoFingerTapGestureRecognizer = GestureManager.GetOrCreateGestureRecognizer(Of TwoFingerTapGestureRecognizer)(uiElement)
recognizer.AddHandler(handler)
element.AddHandler(TwoFingerTapEvent, handler, False)
In example 10 we will wrap the TwoFingerTapEvent and the logic for adding handlers in a helper class called GestureExtensions.
Example 10: Creating a helper class that raises an event when the gesture is performed
public class GestureExtensions
{
private static readonly RoutedEvent TwoFingerTapEvent = EventManager.RegisterRoutedEvent(
"TwoFingerTap",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(TouchManager));
public static void AddTwoFingerTapEventHandler(UIElement element, RoutedEventHandler handler)
{
TwoFingerTapGestureRecognizer recognizer = GestureManager.GetOrCreateGestureRecognizer<TwoFingerTapGestureRecognizer>(element);
recognizer.AddHandler(handler);
element.AddHandler(TwoFingerTapEvent, handler, false);
}
internal static void RaiseTwoFingerTap(UIElement element)
{
element.RaiseEvent(new RoutedEventArgs(TwoFingerTapEvent, element));
}
}
Public Class GestureExtensions
Private Shared ReadOnly TwoFingerTapEvent As RoutedEvent = EventManager.RegisterRoutedEvent("TwoFingerTap", RoutingStrategy.Direct, GetType(RoutedEventHandler), GetType(TouchManager))
Public Shared Sub AddTwoFingerTapEventHandler(element As UIElement, handler As RoutedEventHandler)
Dim recognizer As TwoFingerTapGestureRecognizer = GestureManager.GetOrCreateGestureRecognizer(Of TwoFingerTapGestureRecognizer)(element)
recognizer.AddHandler(handler)
element.AddHandler(TwoFingerTapEvent, handler, False)
End Sub
Friend Shared Sub RaiseTwoFingerTap(element As UIElement)
element.RaiseEvent(New RoutedEventArgs(TwoFingerTapEvent, element))
End Sub
End Class
Here is an example for using the GestureExtensions class and adding logic that is executed when the two-finger tap gesture is activated:
Example 11: Associating the gesture recognizer with a UIElement
public partial class Example : UserControl
{
static Example()
{
GestureManager.RegisterGestureRecognizerFactory(new TwoFingerTapGestureRecognizerFactory());
}
public Example()
{
InitializeComponent();
GestureExtensions.AddTwoFingerTapEventHandler(this.element, this.OnElementTwoFingerTap);
}
private void OnElementTwoFingerTap(object sender, RoutedEventArgs e)
{
this.element.Opacity = this.element.Opacity == 1 ? 0.5 : 1;
}
}
Public Partial Class Example
Inherits UserControl
Shared Sub New()
GestureManager.RegisterGestureRecognizerFactory(New TwoFingerTapGestureRecognizerFactory())
End Sub
Public Sub New()
InitializeComponent()
GestureExtensions.AddTwoFingerTapEventHandler(Me.element, AddressOf Me.OnElementTwoFingerTap)
End Sub
Private Sub OnElementTwoFingerTap(sender As Object, e As RoutedEventArgs)
Me.element.Opacity = If(Me.element.Opacity = 1, 0.5, 1)
End Sub
End Class
The following picture demonstrates the end result - changing the opacity when a two-fingers tap is performed on the Rectangle element.