Community blog feed

Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 2 )

Website
Blog
Mike Taulty
Posted
18 Jun 2009 at 09:49

Summary

Following on from my previous post and remembering that I’m just playing around here and there are better resources up at; which will lead you to a full, sample interop library wrapper. I wanted to carry on a little with my experiment though and move up into the world of touch gestures.

Post extract

Following on from my previous post and remembering that I’m just playing around here and there are better resources up at;

http://code.msdn.microsoft.com/WindowsTouch

which will lead you to a full, sample interop library wrapper.

I wanted to carry on a little with my experiment though and move up into the world of touch gestures.

This seemed easier as it turns out to be the default and there’s no need to call RegisterTouchWindow if you just want WM_GESTURE messages so that’s nice and I changed my test harness and code behind it to reflect that.

You can also use SetGestureConfig and GetGestureConfig in order to control what gestures you’re interested in for a particular Window and so I wrote a bit of code around that ( again, dropping to C++/CLI to do that ) – not entirely sure that this is correct at this point, mind;

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { color: blue; font-weight: normal; } .cb2 { font-weight: normal; }

    1 namespace NativeCodeHelpers {

    2 

    3   public enum class GestureType

    4   {

    5     Zoom = 3,

    6     Pan = 4,

    7     Rotate = 5,

    8     TwoFingerTap = 6,

    9     PressAndTap = 7

   10   };

   11 

   12 }

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { color: blue; font-weight: normal; } .cb2 { font-weight: normal; }

    1 namespace NativeCodeHelpers {

    2 

    3   [Flags]

    4   public enum class PanOptions

    5   {

    6     All = 1,

    7     SingleFingerVertical = 2,

    8     SingleFingerHorizontal = 4,

    9     WithGutter = 8,

   10     WithInertia = 10

   11   };

   12 

   13   public ref class GestureConfigOption

   14   {

   15   public:

   16     property GestureType GestureType;

   17     property bool IsEnabled;

   18   };

   19 

   20   public ref class PanGestureConfigOption : GestureConfigOption

   21   {

   22   public:

   23     PanGestureConfigOption();

   24     property PanOptions PanOptions;

   25   };

   26 

   27   public ref class GestureConfig

   28   {

   29   public:

   30     GestureConfig(IntPtr windowHandle);

   31 

   32     property List<GestureConfigOption^>^ Configuration

   33     {

   34       List<GestureConfigOption^>^ get();     

   35     };

   36 

   37     void AlterConfiguration(List<GestureConfigOption^>^ newConfigItems);

   38 

   39   private:   

   40     IntPtr windowHandle;

   41   };

   42 }

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { font-weight: normal; } .cb2 { color: blue; font-weight: normal; } .cb3 { color: green; font-weight: normal; } .cb4 { color: #a31515; font-weight: normal; }

    1 GestureConfig::GestureConfig(IntPtr windowHandle)

    2 {

    3   this->windowHandle = windowHandle;

    4 }

    5 

    6 List<GestureConfigOption^>^ GestureConfig::Configuration::get()

    7 {

    8   // From the docs, these are the supported values although there appear to be

    9   // more listed.

   10   GESTURECONFIG configs[] =

   11   {

   12     { GID_ZOOM, 0, 0 },

   13     { GID_ROTATE, 0, 0 },

   14     { GID_PAN, 0, 0 },

   15     { GID_TWOFINGERTAP, 0, 0 },

   16     { GID_PRESSANDTAP, 0, 0 }

   17   };

   18   UINT cIds = ARRAYSIZE(configs);

   19 

   20   List<GestureConfigOption^>^ list = gcnew List<GestureConfigOption^>();

   21 

   22   if (::GetGestureConfig((HWND)this->windowHandle.ToPointer(), 0, 0, &cIds,

   23     configs, sizeof(GESTURECONFIG)))

   24   {

   25     for (int i = 0; i < cIds; i++)

   26     {

   27       GestureConfigOption^ option = nullptr;

   28 

   29       switch (configs[i].dwID)

   30       {

   31       case GID_PAN:

   32         {

   33           PanGestureConfigOption^ panOption = gcnew PanGestureConfigOption();

   34           panOption->PanOptions = (PanOptions)configs[i].dwWant;

   35           panOption->IsEnabled = (configs[i].dwWant != 0);

   36           option = panOption;

   37         }

   38         break;

   39       default:

   40         option = gcnew GestureConfigOption();

   41         option->IsEnabled = (configs[i].dwBlock == 0) && (configs[i].dwWant != 0);

   42         break;

   43       }

   44       option->GestureType = (GestureType)configs[i].dwID;

   45       list->Add(option);

   46     }

   47   }

   48   else

   49   {

   50     int x = GetLastError();

   51     throw gcnew InvalidOperationException(L"Failed getting gesture configuration");

   52   }

   53   return(list);

   54 }

   55 

   56 void GestureConfig::AlterConfiguration(List<GestureConfigOption^>^ config)

   57 {

   58   bool failed = false;

   59 

   60   DWORD dwAllPanOptions =

   61     (

   62       GC_PAN | GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY |

   63       GC_PAN_WITH_SINGLE_FINGER_VERTICALLY | GC_PAN_WITH_GUTTER |

   64       GC_PAN_WITH_INERTIA

   65     );

   66 

   67   if ((config) && (config->Count > 0))

   68   {

   69     GESTURECONFIG* pConfig = new GESTURECONFIG[config->Count];

   70     ZeroMemory(pConfig, sizeof(GESTURECONFIG) * config->Count);

   71 

   72     for (int i = 0; i < config->Count; i++)

   73     {

   74       pConfig[i].dwID = (DWORD)config[i]->GestureType;

   75 

   76       pConfig[i].dwWant = (config[i]->IsEnabled) ? 1 : 0;

   77       pConfig[i].dwBlock = (config[i]->IsEnabled) ? 0 : 1;

   78 

   79       if (config[i]->GestureType == GestureType::Pan)

   80       {

   81         PanGestureConfigOption^ panOption = (PanGestureConfigOption^)config[i];

   82 

   83         pConfig[i].dwWant = (DWORD)panOption->PanOptions;

   84 

   85         if (pConfig[i].dwWant == GC_ALLGESTURES)

   86         {

   87           pConfig[i].dwBlock = 0;

   88         }

   89         else

   90         {

   91           pConfig[i].dwBlock = pConfig[i].dwWant ^ dwAllPanOptions;

   92         }

   93       }

   94     }

   95     if (!::SetGestureConfig((HWND)this->windowHandle.ToPointer(), 0, config->Count, pConfig,

   96       sizeof(GESTURECONFIG)))

   97     {

   98       int x = GetLastError();

   99       failed = true;

  100     }   

  101     delete [] pConfig;

  102 

  103     if (failed)

  104     {

  105       throw gcnew InvalidOperationException(L"Faield to set gesture configuration");

  106     }

  107   }

  108 }

  109 

  110 PanGestureConfigOption::PanGestureConfigOption()

  111 {

  112   this->GestureType = NativeCodeHelpers::GestureType::Pan;

  113 }

So, hidden in all that C++/.NET goo is a pretty simple GestureConfig class and I can use it like this in C# to ensure that I get all the touch gestures that I want;

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { color: blue; font-weight: normal; } .cb2 { color: #2b91af; font-weight: normal; } .cb3 { font-weight: normal; }

    1       config = new GestureConfig(this.Handle);

    2 

    3       config.AlterConfiguration(new List<GestureConfigOption>()

    4       {

    5         new GestureConfigOption() { GestureType = GestureType.Zoom, IsEnabled = true },

    6         new GestureConfigOption() { GestureType = GestureType.Rotate, IsEnabled = true },

    7         new GestureConfigOption() { GestureType = GestureType.TwoFingerTap, IsEnabled = true },

    8         new GestureConfigOption() { GestureType = GestureType.PressAndTap, IsEnabled = true },

    9         new PanGestureConfigOption() { PanOptions = PanOptions.All }

   10       });

I then needed a bit of helper code to deal with the WM_GESTURE messages for me using the new  GetTouchInfo functionagain, not entirely sure this C++/CLI is correct but it’s a starting point and here’s the header file followed by the CPP file;

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { color: blue; font-weight: normal; } .cb2 { font-weight: normal; }

    1 namespace NativeCodeHelpers {

    2 

    3   public ref class GestureInfo

    4   {

    5   public:

    6     property GestureType GestureType;

    7     property bool IsBegin;

    8     property bool IsEnd;

    9     property UInt32 X;

   10     property UInt32 Y;

   11 

   12     virtual String^ ToString() override;

   13   };

   14 

   15   public ref class ZoomGestureInfo : GestureInfo

   16   {

   17   public:

   18     property double FingerDistance;

   19 

   20     virtual String^ ToString() override;

   21   };

   22 

   23   public ref class RotateGestureInfo : GestureInfo

   24   {

   25   public:

   26     property double AngleDegrees;

   27 

   28     virtual String^ ToString() override;

   29   };

   30 

   31   public ref class GestureDecoder

   32   {

   33   public:

   34     GestureDecoder(IntPtr wParam, IntPtr lParam);

   35     bool IsGestureMessage();

   36 

   37     property GestureInfo^ Gesture

   38     {

   39       GestureInfo^ get();

   40     };

   41 

   42   private:

   43     IntPtr wParam;

   44     IntPtr lParam;

   45   };

   46 }

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { font-weight: normal; } .cb2 { color: blue; font-weight: normal; } .cb3 { color: #a31515; font-weight: normal; } .cb4 { color: green; font-weight: normal; }

    1 String^ GestureInfo::ToString()

    2 {

    3   return(String::Format("Gesture {0}, X {1}, Y {2}",

    4     this->GestureType, this->X, this->Y));

    5 }

    6 

    7 String^ RotateGestureInfo::ToString()

    8 {

    9   return(String::Format("{0}, Angle {1}", GestureInfo::ToString(),

   10     this->AngleDegrees));

   11 }

   12 

   13 String^ ZoomGestureInfo::ToString()

   14 {

   15   return(String::Format("{0}, Distance {1}", GestureInfo::ToString(),

   16     this->FingerDistance));

   17 }

   18 

   19 GestureDecoder::GestureDecoder(IntPtr wParam, IntPtr lParam)

   20 {

   21   this->wParam = wParam;

   22   this->lParam = lParam;

   23 }

   24 

   25 bool GestureDecoder::IsGestureMessage()

   26 {

   27   GESTUREINFO gestureInfo;

   28   ZeroMemory(&gestureInfo, sizeof(gestureInfo));

   29 

   30   gestureInfo.cbSize = sizeof(gestureInfo);

   31 

   32   BOOL isGestureMessage = ::GetGestureInfo((HGESTUREINFO)lParam.ToPointer(),

   33     &gestureInfo);

   34 

   35   if (isGestureMessage)

   36   {

   37     // TODO: docs not entirely clear to me as to whether we look for BEGIN/END

   38     // in the Flags or in the ID. I'm doing the ID as BEGIN/END on the flags

   39     // looks to be useful.

   40     isGestureMessage = ((gestureInfo.dwID != GID_BEGIN) &&

   41       (gestureInfo.dwID != GID_END));

   42   }

   43   return((bool)isGestureMessage);

   44 }

   45 

   46 GestureInfo^ GestureDecoder::Gesture::get()

   47 {

   48   GestureInfo^ info = nullptr;

   49 

   50   if (!IsGestureMessage())

   51   {

   52     throw gcnew InvalidOperationException(L"Not a gesture message");

   53   }

   54   else

   55   {   

   56     GESTUREINFO gestureInfo;

   57     ZeroMemory(&gestureInfo, sizeof(gestureInfo));

   58     gestureInfo.cbSize = sizeof(gestureInfo);

   59 

   60     if (::GetGestureInfo((HGESTUREINFO)lParam.ToPointer(), &gestureInfo))

   61     {

   62       // TODO: Come back and figure out cbExtraArgs - never seen it set but

   63       // then yet to manage to create a two finger tap or a press and tap.

   64 

   65       switch (gestureInfo.dwID)

   66       {

   67       case GID_ZOOM:

   68         info = gcnew ZoomGestureInfo();

   69         break;

   70       case GID_ROTATE:

   71         info = gcnew RotateGestureInfo();

   72         break;

   73       default:

   74         info = gcnew GestureInfo();

   75         break;

   76       }

   77 

   78       info->GestureType = (GestureType)gestureInfo.dwID;

   79       info->IsBegin = ((gestureInfo.dwFlags & GF_BEGIN) != 0);

   80       info->IsEnd = ((gestureInfo.dwFlags & GF_END) != 0);

   81       info->X = gestureInfo.ptsLocation.x;

   82       info->Y = gestureInfo.ptsLocation.y; 

   83 

   84       if (info->GestureType == GestureType::Zoom)

   85       {

   86         ((ZoomGestureInfo^)info)->FingerDistance = gestureInfo.ullArguments;

   87       }

   88       else if (info->GestureType == GestureType::Rotate)

   89       {

   90         ((RotateGestureInfo^)info)->AngleDegrees =

   91           GID_ROTATE_ANGLE_FROM_ARGUMENT(gestureInfo.ullArguments) *

   92           (180 / Math::PI);

   93       }

   94 

   95       // TODO: dangerous to assume true on these.

   96       ::CloseGestureInfoHandle((HGESTUREINFO)lParam.ToPointer());

   97     }

   98     else

   99     {

  100       // TODO: Some proper exceptions, perhaps?

  101       throw gcnew InvalidOperationException(L"Failed to get gesture info");

  102     }

  103   }

  104   return(info);

  105 }

as an aside, that code comment is wrong – I did figure out how to do a press and tap and a double tap :-)

Now I can plug that code in to my Windows Forms test harness and see if I can get some WM_TOUCH messages dealt with. I altered my TouchPanel class (C#) in order that it now fires a GestureInputReceived event when it spots a gesture and it uses the previous class GestureDecoder to try and decode what gestures it’s actually seeing.

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { color: blue; font-weight: normal; } .cb2 { color: #2b91af; font-weight: normal; } .cb3 { font-weight: normal; } .cb4 { color: #a31515; font-weight: normal; } .cb5 { color: green; font-weight: normal; }

    1   class TouchInputEventArgs : EventArgs

    2   {

    3     public List<TouchInput> TouchInput { get; set; }

    4   }

    5   class GestureInputEventArgs : EventArgs

    6   {

    7     public GestureInfo GestureInfo { get; set; }

    8   }

    9   enum TouchType

   10   {

   11     Touch,

   12     Gesture

   13   }

   14   class TouchPanel : Panel

   15   {

   16     public event EventHandler<TouchInputEventArgs> TouchInputReceived;

   17     public event EventHandler<GestureInputEventArgs> GestureInputReceived;

   18 

   19     public bool Register(TouchType touchType)

   20     {

   21       bool returnValue = true;

   22       this.touchType = touchType;

   23 

   24       if (this.touchType == TouchType.Gesture)

   25       {

   26         InitForGestures();

   27       }

   28       else

   29       {

   30         returnValue = InitForTouch();

   31       }   

   32       return (returnValue);

   33     }

   34     void InitForGestures()

   35     {

   36       config = new GestureConfig(this.Handle);

   37 

   38       config.AlterConfiguration(new List<GestureConfigOption>()

   39       {

   40         new GestureConfigOption() { GestureType = GestureType.Zoom, IsEnabled = true },

   41         new GestureConfigOption() { GestureType = GestureType.Rotate, IsEnabled = true },

   42         new GestureConfigOption() { GestureType = GestureType.TwoFingerTap, IsEnabled = true },

   43         new GestureConfigOption() { GestureType = GestureType.PressAndTap, IsEnabled = true },

   44         new PanGestureConfigOption() { PanOptions = PanOptions.All }

   45       });

   46     }

   47     bool InitForTouch()

   48     {

   49       return (RegisterTouchWindow(this.Handle, 0));

   50     }

   51     public bool Unregister()

   52     {

   53       bool returnValue = touchType == TouchType.Gesture ? true :

   54         UnregisterTouchWindow(this.Handle);

   55 

   56       return (returnValue);

   57     }

   58     protected override void DefWndProc(ref Message m)

   59     {

   60       if ((touchType == TouchType.Touch) && (m.Msg == WM_TOUCH) && (TouchInputReceived != null))

   61       {

   62         TouchInputDecoder decoder = new TouchInputDecoder(m.WParam, m.LParam);

   63 

   64         TouchInputReceived(this, new TouchInputEventArgs()

   65         {

   66           TouchInput = decoder.Inputs

   67         });

   68       }

   69       else if ((touchType == TouchType.Gesture) && (m.Msg == WM_GESTURE) &&

   70         (GestureInputReceived != null))

   71       {

   72         GestureDecoder decoder = new GestureDecoder(m.WParam, m.LParam);

   73 

   74         if (!decoder.IsGestureMessage())

   75         {

   76           base.DefWndProc(ref m);

   77         }

   78         else

   79         {

   80           GestureInfo info = decoder.Gesture;

   81 

   82           GestureInputReceived(this, new GestureInputEventArgs()

   83           {

   84             GestureInfo = info

   85           });

   86         }

   87       }

   88       else

   89       {

   90         base.DefWndProc(ref m);

   91       }

   92     }

   93     TouchType touchType;

   94     GestureConfig config;

   95 

   96     [DllImport("User32")]

   97     private static extern bool RegisterTouchWindow(IntPtr handle, UInt32 flags);

   98 

   99     [DllImport("User32")]

  100     private static extern bool UnregisterTouchWindow(IntPtr handle);   

  101 

  102     // From Winuser.h

  103     const int    WM_TOUCH      = 0x240;

  104     const int    WM_GESTURE    = 0x119;

  105     const UInt32  TWF_FINETOUCH = 1;

  106   }

and then added this to my Form code which uses this TouchPanel and just picks up the “interesting” events as in;

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { color: blue; font-weight: normal; } .cb2 { color: #a31515; font-weight: normal; }

    1   panel.GestureInputReceived += (s, e) =>

    2           {

    3             if (e.GestureInfo.IsBegin)

    4             {

    5               AddDebugTextToTextBox("Begun " + e.GestureInfo.ToString());

    6             }

    7             else if (e.GestureInfo.IsEnd)

    8             {

    9               AddDebugTextToTextBox("Ended " + e.GestureInfo.ToString());

   10             }

   11           };

and so it only syncs itself up to the Begin/End events to avoid “event over-kill”. With all of that tied together, I can re-run my test form and see some touch events coming through nicely ( note – I’ve only managed to achieve Pan, Rotate, Zoom and I’ve yet to see PressAndTap or TwoFingerTap – not sure how to get those with the CodePlex driver I’m using ). Here’s the app reporting some touch gestures;

image

Now, it’d be nice if I actually did something with these gestures but manipulating Windows Forms controls isn’t really much fun especially when it comes to trying to do a rotation whereas it’s a lot easier in (say) WPF and so I moved across to WPF and build a little app that referenced the same C++/CLI library but used it to manipulate a rectangle on a WPF canvas ( note – as I mentioned earlier and in the previous post, there are other ways of doing what we’re doing here – I’m just exploring the gesture side of the API rather than presenting a “right way” of doing something ).

I created a little WPF Window which is just a Rectangle on a Canvas;

<Window
    x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1"
    Height="800"
    Width="600">
    <Canvas
        x:Name="LayoutRoot">
        <Canvas.Resources>
            <Storyboard
                x:Key="sbTwoFingerTap">
                <ColorAnimation
                    Storyboard.TargetName="rectContent"
                    Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
                    From="Yellow"
                    To="Red"
                    Duration="00:00:00.5"
                    AutoReverse="true"
                    FillBehavior="HoldEnd" />
            </Storyboard>
            <Storyboard
                x:Key="sbPressAndTap">
                <ColorAnimation
                    Storyboard.TargetName="rectContent"
                    Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
                    From="Yellow"
                    To="Green"
                    Duration="00:00:00.5"
                    AutoReverse="true"
                    FillBehavior="HoldEnd" />
            </Storyboard>
        </Canvas.Resources>
        <Rectangle
            x:Name="rectContent"
            RadiusX="3"
            RadiusY="3"
            Width="192"
            Height="96"
            Stroke="Orange"
            StrokeThickness="2"
            Canvas.Left="192"
            Canvas.Top="192"
            RenderTransformOrigin="0.5,0.5">
            <Rectangle.Fill>
                <SolidColorBrush
                    Color="Yellow" />
            </Rectangle.Fill>
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <ScaleTransform
                        x:Name="scaleTransform"
                        ScaleX="1"
                        ScaleY="1" />
                    <RotateTransform
                        x:Name="rotateTransform"
                        Angle="0" />
                    <TranslateTransform
                        x:Name="translateTransform" />
                </TransformGroup>
            </Rectangle.RenderTransform>
        </Rectangle>
    </Canvas>
</Window>

and added a little code behind it to use my C++/CLI bits;

.cf { font-family: consolas; font-size: 8pt; color: black; background: white; font-weight: bold; } .cl { margin: 0px; } .cln { color: #2b91af; font-weight: normal; } .cb1 { color: blue; font-weight: normal; } .cb2 { color: #2b91af; font-weight: normal; } .cb3 { font-weight: normal; } .cb4 { color: #a31515; font-weight: normal; }

    1   public partial class Window1 : Window

    2   {

    3     public Window1()

    4     {

    5       InitializeComponent();

    6 

    7       this.Loaded += OnLoaded;

    8     }

    9     void OnLoaded(object sender, RoutedEventArgs e)

   10     {

   11       sbTwoFingerTap = rectContent.FindResource("sbTwoFingerTap") as Storyboard;

   12       sbPressAndTap = rectContent.FindResource("sbPressAndTap") as Storyboard;

   13 

   14       interopHelper = new WindowInteropHelper(this);

   15 

   16       GestureConfig config = new GestureConfig(interopHelper.Handle);

   17       config.AlterConfiguration(new List<GestureConfigOption>()

   18       {

   19         new GestureConfigOption() { GestureType = GestureType.Zoom, IsEnabled = true },

   20         new PanGestureConfigOption() { IsEnabled = true, PanOptions = PanOptions.All },

   21         new GestureConfigOption() { GestureType = GestureType.PressAndTap, IsEnabled = true },

   22         new GestureConfigOption() { GestureType = GestureType.TwoFingerTap, IsEnabled = true },

   23         new GestureConfigOption() { GestureType = GestureType.Rotate, IsEnabled = true }

   24       });

   25 

   26       HwndSource source = HwndSource.FromHwnd(interopHelper.Handle);

   27       HwndSourceHook hook = new HwndSourceHook(WndProc);

   28       source.AddHook(hook);

   29     }

   30     static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam,

   31       ref bool handled)

   32     {

   33       handled = false;

   34 

   35       GestureDecoder decoder = new GestureDecoder(wParam, lParam);

   36 

   37       if (decoder.IsGestureMessage())

   38       {

   39         handled = true;

   40         GestureInfo gi = decoder.Gesture;

   41         Window1 window = (Window1)App.Current.MainWindow;

   42         window.HandleGesture(gi);

   43       }

   44       return (IntPtr.Zero);

   45     }

   46     void HandleGesture(GestureInfo gi)

   47     {

   48       switch (gi.GestureType)

   49       {

   50         case GestureType.Pan:

   51           HandlePan(gi);

   52           break;

   53         case GestureType.PressAndTap:

   54           sbPressAndTap.Stop();

   55           sbPressAndTap.Begin();

   56           break;

   57         case GestureType.Rotate:

   58           HandleRotation(gi);

   59           break;

   60         case GestureType.TwoFingerTap:

   61           sbTwoFingerTap.Stop();

   62           sbTwoFingerTap.Begin();

   63           break;

   64         case GestureType.Zoom:

   65           HandleZoom(gi);

   66           break;

   67         default:

   68           break;

   69       }

   70     }

   71     void HandlePan(GestureInfo gi)

   72     {

   73       if (gi.IsBegin)

   74       {

   75         lastPoint = new Point(gi.X, gi.Y);

   76       }

   77       else

   78       {

   79         Point curPoint = new Point(gi.X, gi.Y);

   80         double xDelta = curPoint.X - lastPoint.X;

   81         double yDelta = curPoint.Y - lastPoint.Y;

   82 

   83         translateTransform.X += xDelta;

   84         translateTransform.Y += yDelta;

   85 

   86         lastPoint = curPoint;

   87       }

   88     }

   89     void HandleZoom(GestureInfo gi)

   90     {

   91       ZoomGestureInfo zi = (ZoomGestureInfo)gi;

   92 

   93       if (zi.IsBegin)

   94       {

   95         firstZoom = zi.FingerDistance;

   96       }

   97       else

   98       {

   99         double delta = zi.FingerDistance - firstZoom;

  100         firstZoom = zi.FingerDistance;

  101         Debug.WriteLine(string.Format("{0}, {1}", DateTime.Now.Ticks, delta));

  102         double scaleX = (delta / scaleSensitivityMagicFactor);

  103         double scaleY = (delta / scaleSensitivityMagicFactor);

  104 

  105         scaleTransform.ScaleX += scaleX;

  106         scaleTransform.ScaleY += scaleY;

  107       }

  108     }

  109     void HandleRotation(GestureInfo gi)

  110     {

  111       RotateGestureInfo ri = (RotateGestureInfo)gi;

  112 

  113       if (ri.IsBegin)

  114       {

  115         captureAngle = true;

  116       }

  117       else

  118       {

  119         if (captureAngle)

  120         {

  121           lastAngle = ri.AngleDegrees;

  122           captureAngle = false;

  123         }

  124         else

  125         {

  126           double delta = ri.AngleDegrees - lastAngle;

  127 

  128           Debug.WriteLine(string.Format("{0}, Delta is {1}",

  129             DateTime.Now.Ticks, delta));

  130 

  131           rotateTransform.Angle += 0 - (delta / rotateSensitivityMagicFactor);

  132 

  133           if (ri.IsEnd)

  134           {

  135             captureAngle = true;

  136             lastAngle = 0;

  137           }

  138         }

  139       }

  140     }

  141     bool captureAngle;

  142     double lastAngle;

  143     double firstZoom;

  144     Point lastPoint;

  145     const double scaleSensitivityMagicFactor = 200.0;

  146     const double rotateSensitivityMagicFactor = 25.0;

  147     WindowInteropHelper interopHelper;

  148     Storyboard sbTwoFingerTap;

  149     Storyboard sbPressAndTap;

  150   }

and that all seemed to work out reasonably well ( notice the magic factors around lines 145/146 ) in that I’ve got an app that’ll respond to the 5 gestures that I’m asking it to;

image

Now, it turns out that using touch to physically manipulate an object like this is a common enough thing to want to do that there’s already a whole API in Windows 7 to handle it – the manipulation API.

As it happens, that API is built as an unmanaged COM API and it’s already wrapped up by the sample interop code up here ( as referenced earlier );

http://code.msdn.microsoft.com/WindowsTouch

and so I think I’ll switch from using my own wrappers now to using those libraries rather than re-inventing that particular wheel.

That’ll be my next post…but in the meantime the source code for where I’ve got to so far is for download from here if you want to just have a play around with it.

We'd love to hear what you think! Submit ideas or give us feedback