admin 管理员组

文章数量: 887006

多點觸控(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/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/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/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)。 

圖1:Windows 7 SDK中的MTGestures範例程式。 


讀者也可以參考另一個MTScratchpadWMTouch範例程式,以及MSDN Library上的Windows Touch章節以了解WM_TOUCH訊息的解讀方式。另外微軟在Windows API Code Pack for Microsoft .NET Framework中還提供了Windows7.Multitouch.dll及Windows7.Multitouch.WPF.dll,讓 .Net平台的程式開發者不必自行處理視窗訊息,直接可使用平常已習慣的程式撰寫模式。

【from http://www.runpc.tw/content/main_content.aspx?mgo=190&fid=E10】

本文标签: 多点 应用程序 触控 WinForm amp