Geeks With Blogs
The Quandary Phase This code was generated by a tool.


FireGestures is one of my favourite Firefox extensions. I have become so accustomed to it of late that I now find web browsing without gesture navigation hopelessly slow.

I have been thinking that it would be nice to start baking gesture navigation into my own applications. Clearly, this is not the kind of feature you would want to shoehorn into any application, but used sparingly and where appropriate it is a great way of short-cutting user navigation and providing a more user-friendly UI experience.

It just so happened that I was working on a WPF application at the time which I felt would benefit from this feature, so this seemed like a good time to start writing my own gesture navigation implementation.

I began by encapsulating a gesture within a class. A gesture, after all, is simply a set of mouse directional movements, carried out in a specific order. As in FireGestures, I am only interested in 4 different gesture directions: up, down, left, right. I captured these in an enumeration named Direction- then implemented the Gesture class itself as a generic list of directions (i.e. a single gesture is a collection of directions). I also overrode the equals and not equal operators for good measure:

   1: public class Gesture : List<Direction>, IEquatable<Gesture>
   2: {
   4:     public Gesture()
   5:     {
   6:     }
   8:     /// <summary>
   9:     /// Overloaded equality operator.
  10:     /// </summary>
  11:     /// <param name="g1"></param>
  12:     /// <param name="g2"></param>
  13:     /// <returns></returns>
  14:     public static bool operator ==(Gesture g1, Gesture g2)
  15:     {
  16:         Gesture g = g1 ?? g2;
  18:         if (object.ReferenceEquals(g, null))
  19:             return true;
  21:         if (object.ReferenceEquals(g1, null) || object.ReferenceEquals(g2, null))
  22:             return false;
  24:         return g1.Equals(g2);
  25:     }
  27:     /// <summary>
  28:     /// Overloaded inequality operator.
  29:     /// </summary>
  30:     /// <param name="g1"></param>
  31:     /// <param name="g2"></param>
  32:     /// <returns></returns>
  33:     public static bool operator !=(Gesture g1, Gesture g2)
  34:     {
  35:         return !(g1 == g2);
  36:     }
  38:     /// <summary>
  39:     /// Parses a string command representation to a gesture object.
  40:     /// </summary>
  41:     /// <param name="value"></param>
  42:     /// <returns></returns>
  43:     public static Gesture Parse(string value)
  44:     {
  45:         if (value == null) return null;
  47:         Gesture g = new Gesture();
  49:         char[] chars = value.ToCharArray();
  51:         foreach (char c in chars)
  52:         {
  53:             switch (c.ToString().ToUpper())
  54:             {
  55:                 case "L":
  56:                     g.Add(Direction.Left);
  57:                     break;
  58:                 case "U":
  59:                     g.Add(Direction.Up);
  60:                     break;
  61:                 case "D":
  62:                     g.Add(Direction.Down);
  63:                     break;
  64:                 case "R":
  65:                     g.Add(Direction.Right);
  66:                     break;
  67:                 default:
  68:                     throw new ArgumentOutOfRangeException(string.Format(
  69:                         "The character value '{0}' could not be converted to a gesture direction.", c.ToString()));
  70:             }
  71:         }
  73:         return g;
  74:     }
  76:     #region IEquatable<Gesture> Members
  78:     public bool Equals(Gesture other)
  79:     {
  80:         if (other == null || this.Count != other.Count) return false;
  82:         bool equal = true;
  84:         for (int i = 0; i < this.Count; i++)
  85:         {
  86:             if (this[i] != other[i])
  87:             {
  88:                 equal = false;
  89:                 break;
  90:             }
  91:         }
  93:         return equal;
  94:     }
  96:     #endregion
  98:     public override int GetHashCode()
  99:     {
 100:         return this.ToString().GetHashCode();
 101:     }
 103:     public override bool Equals(object obj)
 104:     {
 105:         if (obj == null || !(obj is Gesture))
 106:             return false;
 108:         return this.Equals((Gesture)obj);
 109:     }
 111:     public override string ToString()
 112:     {
 113:         return this.ToString(string.Empty);
 114:     }
 116:     public string ToString(string delimiter)
 117:     {
 118:         StringBuilder text = new StringBuilder();
 119:         foreach (Direction dir in this)
 120:         {
 121:             if (text.Length > 0) text.Append(delimiter);
 122:             text.Append(dir.ToString().Substring(0, 1));
 123:         }
 124:         return text.ToString();
 125:     }
 126: }

The backbone of the entire application is the IGestureProvider interface, and its implementing class: GestureProvider. The purpose of the GestureProvider class is to capture mouse locations, and return a Gesture object when the gesture is completed. The interface is structured as follows:

   1: public interface IGestureProvider
   2: {
   4:     /// <summary>
   5:     /// The event raised when a direction change is detected.
   6:     /// </summary>
   7:     event GestureEventHandler GestureDirectionChanged;
   9:     /// <summary>
  10:     /// Gets a value indicating if the provider is currently in point capture mode.
  11:     /// </summary>
  12:     bool IsCapturing { get; }
  14:     /// <summary>
  15:     /// Starts a new capture.
  16:     /// </summary>
  17:     /// <param name="startPoint"></param>
  18:     void StartCapture(Point startPoint);
  20:     /// <summary>
  21:     /// Ends the current capture and returns the completed gesture.
  22:     /// </summary>
  23:     /// <returns></returns>
  24:     Gesture StopCapture();
  26:     /// <summary>
  27:     /// Captures a point.
  28:     /// </summary>
  29:     /// <param name="point"></param>
  30:     void CapturePoint(Point point);
  32: }

So, the idea is: the caller calls StartCapture, passing in a start location, and then calls CapturePoint for each location in the gesture they wish to capture. Each time the direction of the gesture changes, the GestureDirectionChanged event is raised. Finally, when the gesture is complete, the StopCapture method is called, and an object representing the resulting gesture is returned.

The calculation of direction itself is pretty straightforward. Any mouse movement that is heading roughly left (between 225 and 270 degrees from origin) = left, any mouse movement heading roughly right (between 45 and 135 degrees from origin) = right.

The next step was to create a WPF custom control which uses the GestureProvider class, and provides the user interface onto which the gesture will be drawn. This was achieved by deriving from the WPF Canvas class, overriding the OnPreviewMouseRightButtonDown and OnMouseMove methods, and passing the mouse locations while the mouse right button is pressed to an instance of the GestureProvider class.

The final task was to create a test application which ties all of this together. Ideally, I would have liked to have created a WPF FireFox replica, using the WPF tab control and web browser control. Unfortunately, this proved to be rather more tricky than I had bargained for.

The WPF web browser control is basically a WPF wrapper around a COM component. The WinForms web browser control was also a wrapper around the same COM component, and it sucked. I had high hopes for the WPF control, but in all honestly, it sucks too.

The main problem is that of airspace, which in short means that WPF can't draw on top of COM or WinForms controls. I'm sure there are plenty of canny component developers who are already hard at work on a native WPF web browser control, but using the built-in WPF there's no good way to do what I wanted. So, I instead settled for a 'browser-like' application in which you can enter the URL of the page you want to access, and the application retrieves and displays the raw HTML in a textbox...not a particularly useful bit of functionality, but is sufficient to illustrate the point.

In the test application, I have mapped 4 simple gestures: up, down, left and right. I could have extended the example by including some compound gestures i.e. Right-Down, Up-Left etc, and may do this at some point just to demonstrate that the GestureProvider is not limited to single-direction mouse gestures. The gestures are mapped as follows:

  • Up: opens a new tab.
  • Down: closes the current tab.
  • Left: navigates back through the 'browser' history.
  • Right: navigates forward though the 'browser' history.

The source code can be retrieved from the link at the top of the article- feel free to download and play with it.

Posted on Monday, January 26, 2009 5:51 PM | Back to top

Comments on this post: WPF Gesture Navigation

# re: WPF Gesture Navigation
Requesting Gravatar...
Thank you for your post.
This complete your article: You can use this idea to put transparent, yet mouse aware element, over the web-browser and have the gesture work in WPF with real browser.
Left by Ido Ran on Jul 14, 2009 1:51 PM

# classic ugg boots
Requesting Gravatar...
Left by ugg on Oct 31, 2010 3:36 PM

# re: WPF Gesture Navigation
Requesting Gravatar...
thaks your post
Left by ugg evera on Nov 17, 2010 6:45 PM

# re: WPF Gesture Navigation
Requesting Gravatar...
I recently came across your blog and have been reading along. I thought I would leave my first comment. I don't know what to say except that I have enjoyed resading. Nice blog. I will keep visiting this blog very often. The added inshight increases my insights about:Indonesian Furniture | Indonesian Teak Furniture.
Left by Deddy Agoes on Dec 07, 2010 5:23 AM

# re: WPF Gesture Navigation
Requesting Gravatar...
I found your website perfect for my needs. It contains wonderful and helpful posts. I have read most of them and got a lot from them. To me, you are doing the great work.
Left by backlinks on Mar 02, 2011 12:41 AM

Comments have been closed on this topic.
Copyright © Adam Pooler | Powered by: