Community blog feed
Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 2 )
- 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;
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 function – again, 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;
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;
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 );
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.
Related blog entries
-
TechEd EMEA 2008 - Day Four - Windows 7 and more
by Barry Dorrans
-
Microsoft releases IE8 Beta 1 and ASP.NET MVC RC1
by Thushan Fernando
-
Windows 7 Beta SDK and .NET Interop Samples Posted
by Thushan Fernando
-
Process list in XAML plus a bit of virtualisation with the .NET Client Profile
by mtaulty
-
Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 1 )
by mtaulty