程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Windows 7開發:多點觸摸之WM_TOUCH - 本機(動手實驗)

Windows 7開發:多點觸摸之WM_TOUCH - 本機(動手實驗)

編輯:關於.NET

目標

在本動手實驗中,我們將學習如何管理手勢事件,包括:

• 理解使用手勢事件操作對象的含義

• 檢查多點觸摸 硬件是否存在及其就緒情況

• 從手勢 Windows Message 中提取信息

系統要求

要完成本實驗,必須擁有以下工具:

• Microsoft Visual Studio 2008 SP1

• Windows 7

• Windows 7 SDK

• 一台多點觸摸硬件設備

引言

要創建多點觸摸驅動的應用程序,有 3 種方法可供 選擇:“好”、“出色”或“最佳”方法。

“好”方法是這些方法中最簡單的。設計應用程序用戶界面時 應該將觸摸能力考慮在內。可以使用大量基於 Win32 的簡單工具構建一種自然的 界面,以提供更出色的用戶體驗。滾動等觸摸能力來自於 Win32 控件,無需額外 的工作。例如,現在嘗試使用手指滾動您正在閱讀的文檔!這就是“好 ”方法。“出色”方法讓系統接收各種低級觸摸事件,並獲取系 統對這些事件的探索結果。例如,如果用戶在屏幕上做出一個旋轉動作,系統將 發出一個帶有旋轉角度的旋轉手勢。盡管“出色”方法易於使用,但 它具有自身的局限性。使用手勢無法同時實現旋轉、平移和縮放。您也無法同時 處理多個基於不同觸摸方式的操作。例如,兩個用戶操作窗口的不同區域。

“最佳”方法是讀取低級觸摸事件,將其作為應用程序的輸入。 “Piano”等應用程序或可供用戶同時操作的多個滑塊等復雜控件都是 不錯的例子。運行 MS Paint,從工具箱中選擇一個繪制工具,然後使用您的 4 根手指進行繪制(如果硬件支持):

本動手實驗將模仿新的 MS Paint 多點觸摸繪畫特性。我們將使用“最 佳”方法,即讀取並使用原始的觸摸事件,進行低級 WM_TOUCH Multi- touch 消息解碼。

關於 Multi-touch Scratchpad 應用程序

Multi-touch Scratchpad 應用程序提供了一個簡單的窗口,支持使用多 個手指同時繪制連續的線條。在 HOL 文件夾中可以找到本實驗的每項任務所對應 的項目。Starter 文件夾包含實驗需要的文件。本實驗的完成版本位於 Final 文 件夾中。

練習 #1 – 開發多點觸摸應用程序

任務 1 – 創建 Win32 應用程序

1.啟動 Visual Studio 2008 SP1

2.選擇一個新的 C++ Win32 應用程序項目:

3.編譯並運行!

4.我們將使用屬於 Windows 7 的 API 和宏。在 targetver.h 頭文件中將 WINVER 和 _WIN32_WINNT 定義更改為 0x0601

C++

#ifndef WINVER //Specifies that the  minimum required platform is Windows 7
#define WINVER  0x0601         
#endif

#ifndef _WIN32_WINNT  //Specifies that the minimum required platform is Win 7
#define _WIN32_WINNT 0x0601   
#endif

5.編譯並 運行!

任務 2 – 檢查多點觸摸硬件是否存在及其就緒情況

1.我們將開發的應用程序需要一個啟用了觸摸功能的設備。在調用 _tWinMain() 中的 InitInstance() 之前,添加以下代碼來檢查硬件的觸摸功能 及就緒情況:

C++

BYTE digitizerStatus = (BYTE) GetSystemMetrics(SM_DIGITIZER);
if ((digitizerStatus &  (0x80 + 0x40)) == 0) //Stack Ready + MultiTouch
{
   MessageBox(0, L"No touch support is currently availible",  L"Error", MB_OK);
 return 1;
 }

BYTE nInputs  = (BYTE)GetSystemMetrics(SM_MAXIMUMTOUCHES);
wsprintf(szTitle,  L"%s - %d touch inputs", szTitle, nInputs);

2.可以 看到,除了檢查觸摸功能可用性和就緒情況,我們還會找到硬件支持的觸摸輸入 數量。

3.編譯並運行!

任務 3 – 將筆畫源和頭文件添加到項目中,然後使用手指繪制線條

我們想要使用手指作為多點鼠標設備。我們希望觸摸屏幕的每個手指都能 繪制一條線。為此,我們將使用兩個筆畫集合。一個集合保存完成的筆畫(線) ,另一個集合保存正在繪制的線。觸摸屏幕的每個手指向 g_StrkColDrawing 集 合中的一個筆畫添加點。當手指離開屏幕時,我們將該手指的筆畫從 g_StrkColDrawing 移動到 g_StrkColFinished 集合。在 WM_PAINT 上繪制兩個 集合。

1.在 Starter 文件夾中可以找到兩個文件:Stroke.h 和 Stroke.cpp。將它 們復制到項目文件夾,使用“Add Existing item…” 將它們添加到項目中。

2.在 MTScratchpadWMTouch.cpp 文件開頭添加一 行:#include "Stroke.h"

C++

#include  "Stroke.h"

3.在 mtGesture.cpp 文件開頭的 //Global Variables: 部分添加一個全局變量定義:

C++

CStrokeCollection g_StrkColFinished; // The  user finished entering strokes.
                                     // The user lifted his  or her finger.
CStrokeCollection g_StrkColDrawing; // The  Strokes collection the user is
                                   // currently  drawing.

4.將以下行添加到 WndProc() 中,注意 WM_already PAINT 已經由應用程序向導創建:

C++

case WM_PAINT:
                                 hdc = BeginPaint(hWnd,  &ps);
 // Full redraw: draw complete collection of  finished strokes and
            // also all the  strokes that are currently in drawing.
              g_StrkColFinished.Draw(hdc);
 g_StrkColDrawing.Draw (hdc);
 EndPaint(hWnd, &ps);
 break;

5.現 在可以啟用 WM_TOUCH 消息了。默認情況下,窗口會接收 WM_GESTURE 消息。要 切換到低級 WM_TOUCH 消息,需要調用 RegisterTouchWindow() API。將以下代 碼添加到 InitInstance() 函數中 ShowWindow() 調用之前:

C++

// Register the application window for  receiving multi-touch input.
if (!RegisterTouchWindow(hWnd,  0))
{
   MessageBox(hWnd, 
      L"Cannot  register application window for touch input", L"Error",  MB_OK);
 return FALSE;
 }

6.我們要求 Windows 發送 WM_TOUCH 消息。WM_TOUCH 消息比較特殊。除非要求系統不收集消息中的多 點觸摸事件(參見 TWF_FINETOUCH 參數),否則您將得到消息中的所有觸摸點。 這是合理的,因為用戶在同時通過多個觸摸點觸摸屏幕。將以下行添加到 WndProc() 函數中:

C++

case WM_TOUCH:
{
    // A WM_TOUCH  message can contain several messages from different  contacts
   // packed together.
    unsigned int  numInputs = (int) wParam; //Number of actual contact  messages
    TOUCHINPUT* ti = new TOUCHINPUT[numInputs];  // Allocate the storage for
                                                //the  parameters of the per-
                                               //contact  messages

    // Unpack message parameters into the  array of TOUCHINPUT structures, each
   // representing  a message for one single contact.
    if  (GetTouchInputInfo((HTOUCHINPUT)lParam, numInputs, ti,
                                                           sizeof(TOUCHINPUT)))
     {
        // For each contact, dispatch the message  to the appropriate message
       // handler.
         for(unsigned int i=0; i<numInputs; ++i)
         {
            if (ti[i].dwFlags &  TOUCHEVENTF_DOWN)
            {
                 OnTouchDownHandler(hWnd, ti[i]);
 }
             else if (ti[i].dwFlags & TOUCHEVENTF_MOVE)
            {
                 OnTouchMoveHandler(hWnd, ti[i]);
 }
             else if (ti[i].dwFlags & TOUCHEVENTF_UP)
             {
                OnTouchUpHandler(hWnd,  ti[i]);
 }
        }
    }
     CloseTouchInputHandle((HTOUCHINPUT)lParam);
 delete [] ti;
 }
break;

7.wParam 保存 WM_TOUCH 消息中包含的觸摸輸入數量。GetTouchInputInfo() API 使用每個觸摸點的觸摸信息填充 TOUCHINPUT 數組。從 TOUCHINPUT 數組提 取完數據之後,您需要調用 CloseTouchInputHandle() 來釋放系統資源。下面是 TOUCHINPUT 結構的定義:

C++  (Extracted from WinUser.h)

typedef struct tagTOUCHINPUT {
    LONG x;
 LONG y;
 HANDLE hSource;
 DWORD dwID;
 DWORD  dwFlags;
 DWORD dwMask;
 DWORD dwTime;
 ULONG_PTR  dwExtraInfo;
 DWORD cxContact;
 DWORD cyContact;
 }  TOUCHINPUT, *PTOUCHINPUT;
typedef TOUCHINPUT const *  PCTOUCHINPUT;

/*
 * Conversion of touch input  coordinates to pixels
 */
#define TOUCH_COORD_TO_PIXEL(l)          ((l) / 100)

/*
 * Touch input  flag values (TOUCHINPUT.dwFlags)
 */
#define  TOUCHEVENTF_MOVE            0x0001
#define  TOUCHEVENTF_DOWN            0x0002
#define  TOUCHEVENTF_UP              0x0004

我們主要 關注 TOUCHINPUT 結構中的 4 個參數:x 和 y 是觸摸位置的屏幕乘以 100 後的 值。這意味著我們需要將每個坐標值除以 100(或使用 TOUCH_COORD_TO_PIXEL()   宏)並調用 ScreenToClient() 來轉換為窗口坐標體系。請注意,如果屏幕設 置為高 DPI(大於 96 DPI),您可能還需要將坐標值除以 96 並乘以當前 DPI。 為了簡單起見,我們在應用程序中跳過這一步。其他兩個參數是 dwID 和 dwFlags。dwFlags 告訴我們觸摸輸入的類型:向下、移動或向上。在我們的應用 程序中,TOUCHEVENTF_DOWN 開始一個新筆畫,TOUCHEVENTF_MOVE 將另一個點添 加到現有筆畫中,而 TOUCHEVENTF_UP 完成一個筆畫並將其移動到 g_StrkColFinished 集合。最後一個參數是 dwID,這是觸摸輸入標識符。但手指 首次觸摸屏幕時,會生成一個與該手指相關聯的唯一觸摸 ID。以後來自該手指的 所有觸摸輸入都具有相同的唯一 ID,直到最後的 TOUCHEVENTF_UP 輸入。當手指 離開屏幕時,該 ID 被釋放並可重用為後來觸摸屏幕的其他手指的唯一 ID。現在 我們來處理觸摸輸入,將以下函數添加到 WndProc() 函數之前:

C++

// Returns color for the newly started  stroke.
// in:
//      bPrimaryContact     flag,  whether the contact is the primary contact
// returns:
//      COLORREF, color of the stroke
COLORREF  GetTouchColor(bool bPrimaryContact)
{
    static int  s_iCurrColor = 0;    // Rotating secondary color index
    static COLORREF s_arrColor[] =  // Secondary colors  array
    {
        RGB(255, 0, 0),              // Red
        RGB(0, 255, 0),              // Green
        RGB(0, 0, 255),              // Blue
        RGB(0, 255, 255),            // Cyan
        RGB(255, 0, 255),            // Magenta
        RGB(255, 255, 0)             // Yellow
    };

     COLORREF color;
 if (bPrimaryContact)
    {
         // The application renders the primary contact in  black.
        color = RGB(0,0,0);         //  Black
    }
    else
    {
         // Take the current secondary color.
         color = s_arrColor[s_iCurrColor];

        // Move  to the next color in the array.
         s_iCurrColor = (s_iCurrColor + 1) %
                                  (sizeof(s_arrColor)/sizeof (s_arrColor[0]));
 }
    return color;
 }

// Extracts contact point in client area coordinates  (pixels) from a 
// TOUCHINPUT structure.
// in:
//      hWnd        window handle
//       ti          TOUCHINPUT structure (info about contact)
// returns:
//      POINT with contact coordinates
POINT GetTouchPoint(HWND hWnd, const TOUCHINPUT& ti)
{
    POINT pt;
 pt.x = TOUCH_COORD_TO_PIXEL (ti.x);
 pt.y = TOUCH_COORD_TO_PIXEL(ti.y);
  ScreenToClient(hWnd, &pt);
 return pt;
 }

// Handler for touch-down input.
// in:
//       hWnd        window handle
//      ti           TOUCHINPUT structure (info about contact)
void  OnTouchDownHandler(HWND hWnd, const TOUCHINPUT& ti)
{
    // Create a new stroke, add a point, and assign a  color to it.
    CStroke strkNew;
 POINT p =  GetTouchPoint(hWnd, ti);

    strkNew.AddPoint(p);
 strkNew.SetColor(GetTouchColor((ti.dwFlags &  TOUCHEVENTF_PRIMARY) != 0));
 strkNew.SetId(ti.dwID);

    // Add the new stroke to the collection of strokes  being drawn.
    g_StrkColDrawing.AddStroke(strkNew);
  }

// Handler for touch-move input.
// in:
//       hWnd        window handle
//      ti           TOUCHINPUT structure (info about contact)
void OnTouchMoveHandler(HWND hWnd, const TOUCHINPUT& ti)
{
    // Find the stroke in the collection of the  strokes being drawn.
    int iStrk =  g_StrkColDrawing.FindStrokeById(ti.dwID);
 POINT p =  GetTouchPoint(hWnd, ti);

    // Add the contact  point to the stroke.
    g_StrkColDrawing[iStrk].AddPoint (p);

    // Partial redraw: redraw only the last  line segment.
    HDC hDC = GetDC(hWnd);
  g_StrkColDrawing[iStrk].DrawLast(hDC);
 ReleaseDC(hWnd, hDC);
 }

// Handler for touch-up message.
// in:
//      hWnd        window handle
//       ti          TOUCHINPUT structure (info about contact)
void OnTouchUpHandler(HWND hWnd, const TOUCHINPUT& ti)
{

    // Find the stroke in the collection of  the strokes being drawn.
    int iStrk =  g_StrkColDrawing.FindStrokeById(ti.dwID);

    // Add  the finished stroke to the collection of finished strokes.
    g_StrkColFinished.AddStroke(g_StrkColDrawing[iStrk]);

    // Remove finished stroke from the collection of  strokes being drawn.
    g_StrkColDrawing.RemoveStroke (iStrk);

    // Redraw the window.
     InvalidateRect(hWnd, NULL, FALSE);
 } 

8.為了使繪 制的筆畫顯得更有意思,我們為每個唯一 ID 挑選了一種不同的顏色。主觸摸手 指是第一個觸摸屏幕的手指。它是一種特殊的輸入,因為它充當著鼠標指針。我 們為這個手指的 ID 選擇了黑色。編譯應用程序並運行!現在您可以觸摸屏幕了 !

小結

在本實驗中,我們學習了如何使用低級 WM_TOUCH 消息。 您了解了如何檢查是否存在多點觸摸設備;學習了如何配置一個窗口來獲得 WM_TOUCH 消息,如何從消息提取輸入,以及系統如何將觸摸 ID 與觸摸輸入相關 聯。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved