目標
在本動手實驗中,我們將學習如何管理手勢事件,包括:
• 理解使用手勢事件操作對象的含義
• 檢查多點觸摸 硬件是否存在及其就緒情況
• 從手勢 Windows Message 中提取信息
系統要求
要完成本實驗,必須擁有以下工具:
• Microsoft Visual Studio 2008 SP1
• Windows 7
• Windows 7 SDK
• 一台多點觸摸硬件設備
引言
要創建多點觸摸驅動的應用程序,有 3 種方法可供 選擇:“好”、“出色”或“最佳”方法。
“好”方法是這些方法中最簡單的。設計應用程序用戶界面時 應該將觸摸能力考慮在內。可以使用大量基於 Win32 的簡單工具構建一種自然的 界面,以提供更出色的用戶體驗。滾動等觸摸能力來自於 Win32 控件,無需額外 的工作。例如,現在嘗試使用手指滾動您正在閱讀的文檔!這就是“好 ”方法。
“最佳”方法是讀取低級觸摸事件,將其作為 應用程序的輸入。“Piano”等應用程序或可供用戶同時操作的多個滑 塊等復雜控件都是不錯的例子。運行 MS Paint,從工具箱中選擇一個繪制工具, 然後使用您的 4 根手指進行繪制(如果硬件支持):
在本動手實驗中,我們將使用“出色”方法。“出色” 方法是為應用程序獲取觸摸事件的最簡單方式,可用於自定義縮放、旋轉和平移 等操作,無需讀取和操作原始的觸摸事件。讓我們來立即體驗多點觸摸手勢!
關於 Multitouch Gesture 應用程序
Multitouch Gesture 應用程 序提供了一個簡單的窗口,其中包含一個矩形。在 HOL 文件夾中,可以找到本實 驗的每項任務所對應的項目。Starter 文件夾包含實驗需要的文件。本實驗的完 成版本位於 Final 文件夾中。
此應用程序通過與一個著色的矩形交互來響應多點觸摸手勢輸入。該矩 形能夠響應以下手勢:
平移
要平移圖像,將兩個手指放在應用程 序窗口中並朝著希望移動的方向拖動。確保在兩個手指之間保留少量空間,以便 觸摸界面能夠將它們看作獨立的接觸點。
旋轉
使用兩個手指觸摸矩形,然後旋轉手指形成一個圓。
縮放
使用兩個手指觸摸矩形,然後讓兩個手指相互靠近或遠離 。
雙指點擊
使用兩個手指同時點擊一次,可以在紅色矩形中顯示 或隱藏對角線。
雙指交替點擊
首先使用一個手指按住矩形,然後使用另一個手 指點擊矩形,再移開第一個手指,這樣可以更改矩形的顏色。
練習 #1 :開發多點觸摸應用程序
任務 1 – 創建 Win32 應用程序
1.啟動 Visual Studio 2008 SP1
2.選擇一個新的 C++ Win32 應用程序項目:
3.編譯並運行
4.我們將使用屬於 Windows 7 的 API 和宏。
a. 在 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 available", L"Error", MB_OK);
return 1;
}
BYTE nInputs = (BYTE)GetSystemMetrics(SM_MAXIMUMTOUCHES);
wsprintf(szTitle, L"%s - %d touch inputs", szTitle, nInputs);
2.可以 看到,除了檢查觸摸功能可用性和就緒情況,我們還會找到硬件支持的觸摸輸入 數量。
3.編譯並運行
任務 3 – 將繪制對象源和頭文件添加到項目,然後繪制矩形
在 Starter 文件夾中可以找到兩個文件:DrawingObject.h 和 DrawingObject.cpp 。將它們復制到項目文件夾,使用“Add Existing item…”將它們添加到項目中。
1.在 mtGesture.cpp 文件開頭添加一行:#include DrawingObject.h
C++
#include "DrawingObject.h"
2.在 mtGesture.cpp 文件開頭的 //Global Variables: 部分添加一個全局變量定義:
C++
CDrawingObject g_drawingObject;
3.將以下行添加到 WndProc 中,注意 WM_already PAINT 已經由應用程序向導創建:
C++
case WM_SIZE:
g_drawingObject.ResetObject(LOWORD(lParam), HIWORD(lParam));
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
g_drawingObject.Paint (hdc);
EndPaint(hWnd, &ps);
break;
4.編譯並運行
5.更改 Windows 的大小以觸發發送給應用程序的 WM- Paint 消息。窗口中央應該出現一個紅色矩形:
任務 4 – 立即開始觸摸!
可以開始使用應用程序了。默認情況 下,啟用了觸摸功能的系統會向目標窗口提供 WM_GESTURE 消息。這有點類似於 鼠標和鍵盤消息。系統使用低級觸摸輸入事件並計算生成的手勢。使用 lParam 參數作為句柄來提取手勢信息。GetGestureInfo() API 獲取 lParam 手勢句柄和 GESTUREINFO 結構變量的一個地址:
C++(摘自 WinUser.h)
typedef struct tagGESTUREINFO {
UINT cbSize; // size, in bytes, (including variable length Args field)
DWORD dwFlags; // see GF_* flags
DWORD dwID; // gesture ID, see GID_* defines
HWND hwndTarget; // handle to window targeted by this gesture
POINTS ptsLocation; // current location of this gesture
DWORD dwInstanceID; // internally used
DWORD dwSequenceID; // internally used
ULONGLONG ullArguments; // arguments for gestures (8 BYTES)
UINT cbExtraArgs; // size, of extra arguments, that accompany this gesture
} GESTUREINFO, *PGESTUREINFO;
typedef GESTUREINFO const * PCGESTUREINFO;
/*
* Gesture flags - GESTUREINFO.dwFlags
*/
#define GF_BEGIN 0x00000001
#define GF_INERTIA 0x00000002
#define GF_END 0x00000004
/*
* Gesture IDs
*/
#define GID_BEGIN 1
#define GID_END 2
#define GID_ZOOM 3
#define GID_PAN 4
#define GID_ROTATE 5
#define GID_TWOFINGERTAP 6
#define GID_PRESSANDTAP 7
#define GID_ROLLOVER GID_PRESSANDTAP
使用了通過調用 GetGestureInfo() 獲得的信息之後,我們必須調用 CloseGestureInfoHandle() 來釋放系統分配的內存塊。
當響應手勢消息 時,dwFlags 和 dwID 兩個字段非常重要。dwID 告訴我們用戶執行了何種手勢: 縮放、平移、旋轉等。dwFlags 告訴我們這是它第一次通知我們該手勢,還是最 後一次,或者是否用戶已從屏幕移開了手指,但慣性引擎仍在發出手勢消息。有 許多簡單手勢(比如“雙指點擊”),應用程序對它們只需響應一次 ;其他手勢稍微復雜一些,因為它們在用戶操作期間發送了許多消息。對於這類 手勢(縮放、旋轉和平移),我們需要根據不同的條件采取不同的響應方式。對 於平移和縮放,我們不會對一系列手勢中的第一個采取任何操作。表示旋轉開始 的消息附帶一個旋轉角度,我們需要根據該角度來旋轉繪制對象。只要一個手勢 不是一系列手勢中的第一個,我們就要計算上一個參數與新參數之間的差值,並 提取縮放系數、平移范圍或相對旋轉角度。通過這種方式,我們可以在用戶使用 手指觸摸屏幕並移動手指時更新應用程序。
1.讓我們來移動對象!將以下 代碼添加到 WndProc 中:
C++
case WM_GESTURE:
return ProcessGestureMessage(hWnd, wParam, lParam);
2.將以下函數添加到 WndProc 之前:
C++
LRESULT ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
static POINT lastPoint;
static ULONGLONG lastArguments;
GESTUREINFO gi;
gi.cbSize = sizeof (GESTUREINFO);
gi.dwFlags = 0;
gi.ptsLocation.x = 0;
gi.ptsLocation.y = 0;
gi.dwID = 0;
gi.dwInstanceID = 0;
gi.dwSequenceID = 0;
BOOL bResult = GetGestureInfo((HGESTUREINFO)lParam, &gi);
switch(gi.dwID)
{
case GID_PAN:
if ((gi.dwFlags & GF_BEGIN) == 0) //not the first message
{
g_drawingObject.Move (gi.ptsLocation.x - lastPoint.x,
gi.ptsLocation.y - lastPoint.y);
InvalidateRect(hWnd, NULL, TRUE);
}
}
//Remember last values for delta calculations
lastPoint.x = gi.ptsLocation.x;
lastPoint.y = gi.ptsLocation.y;
lastArguments = gi.ullArguments;
CloseGestureInfoHandle((HGESTUREINFO) lParam);
return 0;
}
3.編譯並運行
4.嘗試使用兩個手指移動對象,可以看到,對象在跟隨 手指移動。您應該了解以下事實:
a.觸摸位置由屏幕坐標確定。我們感興 趣的是偏移量,所以我們並不關心精確的位置坐標,如果我們需要了解精確位置 來進行調整,就要調用 ScreenToClient()。
b.嘗試在不觸摸對象的情況 下移動它,觸摸窗口的空白區域。它移動了!我們沒有執行“點擊測試 ”來檢查觸摸位置是否在對象內部。執行點擊測試需要窗口中的位置坐標。
5.完成應用程序並響應所有手勢 ID:
C++
LRESULT ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
static POINT lastPoint;
static ULONGLONG lastArguments;
GESTUREINFO gi;
gi.cbSize = sizeof(GESTUREINFO);
gi.dwFlags = 0;
gi.ptsLocation.x = 0;
gi.ptsLocation.y = 0;
gi.dwID = 0;
gi.dwInstanceID = 0;
gi.dwSequenceID = 0;
BOOL bResult = GetGestureInfo ((HGESTUREINFO)lParam, &gi);
switch(gi.dwID)
{
case GID_PAN:
if ((gi.dwFlags & GF_BEGIN) == 0) //not the first message
{
g_drawingObject.Move(gi.ptsLocation.x - lastPoint.x,
gi.ptsLocation.y - lastPoint.y);
// repaint the rect
InvalidateRect(hWnd, NULL, TRUE);
}
break;
case GID_ZOOM:
//not the first message
if ((gi.dwFlags & GF_BEGIN) == 0 && lastArguments != 0)
{
double zoomFactor = (double) gi.ullArguments/lastArguments;
// Now we process zooming in/out of object
g_drawingObject.Zoom(zoomFactor, gi.ptsLocation.x, gi.ptsLocation.y);
InvalidateRect(hWnd,NULL,TRUE);
}
break;
case GID_ROTATE:
{
//The angle is the rotation delta from the last message,
//or from 0 if it is a new message
ULONGLONG lastAngle = ((gi.dwFlags & GF_BEGIN) != 0) ? 0 : lastArguments
int angle = (int)(gi.ullArguments – lastAngle);
g_drawingObject.Rotate(
GID_ROTATE_ANGLE_FROM_ARGUMENT (angle),
gi.ptsLocation.x, gi.ptsLocation.y);
InvalidateRect (hWnd, NULL, TRUE);
}
break;
case GID_PRESSANDTAP:
g_drawingObject.ShiftColor();
InvalidateRect(hWnd, NULL, TRUE);
break;
case GID_TWOFINGERTAP:
g_drawingObject.TogleDrawDiagonals();
InvalidateRect(hWnd, NULL, TRUE);
break;
}
//Remember last values for delta calculations
lastPoint.x = gi.ptsLocation.x;
lastPoint.y = gi.ptsLocation.y;
lastArguments = gi.ullArguments;
CloseGestureInfoHandle((HGESTUREINFO)lParam);
return 0;
}
6.編譯並運行
7.測試所 有手勢。
任務 5 – 存在一個 Bug !
1.嘗試旋轉對象。結 果如何?根據定義,窗口將接收除旋轉之外的所有手勢。我們可以配置觸摸引擎 來僅提供可用的手勢或所有手勢。
2.將以下代碼添加到 InitInstance() 函數中的 ShowWindow() 調用之前,以啟用包括 GID_ROTATE 在內的所有手勢類 型:
C++
//Enable all gesture types
GESTURECONFIG gestureConfig;
gestureConfig.dwID = 0;
gestureConfig.dwBlock = 0;
gestureConfig.dwWant = GC_ALLGESTURES;
BOOL result = SetGestureConfig(hWnd, 0, 1, &gestureConfig,sizeof(gestureConfig));
3.編譯並運行
4.嘗試旋轉對象。大功告成!
小結
在本實驗中,我們學習 了如何使用觸摸手勢消息來操作屏幕上的對象。您了解了如何檢查是否存在多點 觸摸硬件;如何配置一個窗口來獲得想要的手勢事件;以及如何提取和操作手勢 參數。
祝您實驗愉快!