多點觸控(Multi Touch)是微軟新一代作業系統Windows 7較受關注的特點之一,本文將使用Visual C# 2008建立一個Windows Form應用程式,並說明如何取得觸控動作相關資訊。
在Windows 7的Home Premium、Professional、Enterprise和Ultimate版本中,只要搭配支援多點觸控的硬體即可使用多點觸控功能。而應用程式方面,對觸控的支援則可分為三個等級:Good、Better和Best(請參考表1)。
Good等級表示不需要修改程式碼可直接支援數個基本的手勢;Better等級要能針對各種不同的手勢做出合理自然的互動;Best等級則是使用更進階的功能處理更詳細的資訊,自訂各種觸控操作經驗。
Windows 7支援的手勢有9種(參考表2),我們可以看到除了Rotate和Two-Finger Tap之外,其餘手勢都可以對應到傳統的滑鼠或鍵盤的操作,也就是說應用程式不需大幅修改既有的程式,即可支援Windows 7大部分的手勢,然而你也可以在程式中重新定義這些手勢所對應的動作。
如何取得設備資訊?
在開始處理觸控動作前,你可以呼叫GetSystemMetrics並傳入SM_DIGITIZER來得知目前電腦的觸控支援能力(參考程式1),根據GetSystemMetrics的傳回值,即可得知相關資訊(參考表3)。若你沒有適當的觸控硬體,可以使用Multi-Touch Vista這個軟體搭配2隻滑鼠模擬2點輸入。
程序1
const int SM_DIGITIZER = 94;
[DllImport("user32")]
static extern int GetSystemMetrics(int n);
bool SupportMultiTouch()
{
int r = GetSystemMetrics(SM_DIGITIZER);
if ((r & 0x40) != 0)
return true;
else
return false;
}
如何取得觸控訊息?
為了支援觸控動作,Windows 7定義了2個新的視窗訊息:WM_GESTURE與WM_TOUCH。Windows 7中的視窗預設只會收到WM_GESTURE訊息,若呼叫了RegisterTouchWindow之後則會變成只會收到WM_TOUCH訊息,然而本文將專注於探討WM_GESTURE訊息。
由於目前的WinForm尚未將這2個訊息轉換為控制項的事件,所以為了處理這2個訊息,最直接的方式就是覆寫控制項的WndProc方法。在本文的範例中,我們覆寫Form的WndProc方法,如此便能處理表單上的觸控動作(參考程式2)。
程序2
override void WndProc(ref Message m)
{
bool handled = true;
switch (m.Msg)
{
case WM_GESTURENOTIFY:
// 可在此呼叫SetGestureConfig
break;
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
// 可在此決定是否停用Flicks或其他功能
break;
case WM_GESTURE:
handled = DecodeGesture(ref m);
break;
default:
handled = false;
break;
}
if (handled)
m.Result = new IntPtr(1);
else
base.WndProc(ref m);
}
若你要讓其他控制項支援觸控動作,則必須另外繼承該控制項,並覆寫其WndProc方法,或是在表單的WndProc方法中實作判斷觸控目的控制項的邏輯。
在收到一個手勢的第一個WM_GESTURE訊息前,程式會先收到一個WM_GESTURENOTIFY訊息,你可以在此時呼叫SetGestureConfig並帶入GESTURECONFIG結構,設定目前要接受或忽略哪些手勢。
也可以在視窗一載入時就先設定好(參考程式3)。視窗預設不會收到關於Rotate手勢的訊息,若想收到所有手勢的訊息則必須呼叫 SetGestureConfig 做設定,詳細的設定項目請參考:http://msdn.microsoft.com/library/dd353241.aspx。
程序3
GESTURECONFIG gc = new GESTURECONFIG();
gc.dwID = 0;
gc.dwWant = GC_ALLGESTURES;
gc.dwBlock = 0;
SetGestureConfig(this.Handle, 0, 1, ref gc, _gestureConfigSize);
如何解讀觸控資訊?
在手勢作用的過程中成是會收到多個WM_GESTURE訊息,其所帶的參數可用來取得GESTUREINFO結構(參考程式4),其中比較重要的部分是dwFlags、dwID、ptsLocation以及ullArguments。dwID可用來分辨是哪種手勢(參考表4),dwFlags表示手勢的狀態(開始、慣性動作、停止),ptsLocation及ullArguments則根據不同的手勢有不同的意義,詳細資訊請參考:http://msdn.microsoft.com/library/dd353242.aspx。
程序4
[StructLayout(LayoutKind.Sequential)]
struct GESTUREINFO
{
public int cbSize;
public int dwFlags; // GF_*
public int dwID; // GID_*
public IntPtr hwndTarget;
[MarshalAs(UnmanagedType.Struct)]
internal POINTS ptsLocation;
public int dwInstanceID;
public int dwSequenceID;
public Int64 ullArguments;
public int cbExtraArgs;
}
在此我們只看到5種Gesture ID,但前文卻提到了9種手勢,這是因為Flicks (筆觸,根據滑動方向提供一組功能)是對應到Pan手勢或鍵盤動作,而其他3種手勢只會引發單純的滑鼠事件。在滑鼠事件處理常式中,利用GetMessageExtraInfo可得知事件是由滑鼠還是觸控所產生,進而做不同的處理(參考程式5)。
程序5
const uint MOUSEEVENTF_FROMTOUCH = 0xff515700;
[DllImport("user32")]
static extern IntPtr GetMessageExtraInfo();
bool IsFromTouch()
{
IntPtr p = GetMessageExtraInfo();
return MOUSEEVENTF_FROMTOUCH == ((uint)p & MOUSEEVENTF_FROMTOUCH);
}
本文中的範例會於收到WM_GESTURE訊息時,在DecodeGesture方法中利用GetGestureInfo來取得與手勢相關的GESTUREINFO結構,並根據不同的手勢做對應的動作(參考程式6)。需注意的是,處理完之後必須呼叫CloseGestureInfoHandle關閉GESTUREINFO的handle,否則會造成記憶體遺漏。
程序6
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetGestureInfo(IntPtr hGestureInfo, ref GESTUREINFO pGestureInfo);
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseGestureInfoHandle(IntPtr hGestureInfo);
bool DecodeGesture(ref Message m)
{
bool handled = true;
GESTUREINFO gi;
gi = new GESTUREINFO();
gi.cbSize = _gestureInfoSize;
GetGestureInfo(m.LParam, ref gi);
switch (gi.dwID)
{
case GID_ZOOM: //處理縮放
break;
case GID_PAN: //處理平移
break;
case GID_ROTATE: //處理旋轉
break;
case GID_TWOFINGERTAP: //處理兩指點擊
break;
case GID_PRESSANDTAP: //處理press & tap
break;
default:
handled = false;
break;
}
if (handled)
CloseGestureInfoHandle(m.LParam);
return handled;
}
處理Pan手勢的小技巧
在處理Pan手勢時,為了避免Windows將手勢誤判為Flicks,造成不良的使用經驗,最好暫時關閉Flicks的功能。此動作可以在控制項載入時就呼叫SetProp進行設定(參考程式7),或是在WndProc中收到WM_TABLET_QUERYSYSTEMGESTURESTATUS訊息時設定m.Result為TABLET_DISABLE_FLICKS(參考程式8)。其他可設定的項目請參考http://msdn.microsoft.com/en-us/library/bb969148.aspx。
程序7
[DllImport("user32")]
static extern bool SetProp(IntPtr hWnd, string lpString, IntPtr hData);
SetProp(this.Handle, "MicrosoftTabletPenServiceProperty", new IntPtr(0x10000));
程序8
const int TABLET_DISABLE_FLICKS = 0x00010000;
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
m.Result = (IntPtr)TABLET_DISABLE_FLICKS;
break;
結語
了解以上處理WM_GESTURE訊息的方式之後,即可試著閱讀並修改Windows 7 SDK中的MTGestures範例程式。該範例會在表單上繪製一個長方形,並依據不同的手勢對表單上的長方形做出不同的動作(參考圖1)。