本节包含有关 Windows Touch 笔势的主题。包括Windows Touch 笔势概述(描述 Windows Touch 所支持的各种笔势);Windows Touch 笔势入门(介绍使用 Windows Touch 笔势的基本步骤);对使用滚动条进行平移的旧版支持(说明如何确保应用程序将正确解释平移笔势);改进单指平移体验(说明如何自定义平移体验)。
Windows Touch 笔势概述
笔势概述
Windows Touch 启用了一些支持单个触控点和多个触控点的笔势。下图演示了 Windows 7 中支持的各种笔势。
注意 当各个触控点之间的距离更远时,使用一些识别器来解释带多个触控点的笔势更为可靠。
旧版支持
对于旧版支持,默认笔势处理程序会将一些笔势映射到早期版本的 Windows 中使用的 Windows 消息。下表概述了笔势映射到旧版消息的方式。
笔势 | 说明 | 生成的消息 |
---|---|---|
平移 | 平移笔势对应于使用滚轮。 | WM_VSCROLL WM_HSCROLL |
按住 | 按住笔势对应于右击鼠标。 | WM_RBUTTONDOWN WM_RBUTTONUP |
缩放 | 缩放笔势触发的消息与按住 Ctrl 键并旋转鼠标轮以进行滚动所触发的消息类似。 | WM_MOUSEWHEEL(在 lParam 中设置了 MK_CONTROL) |
解释 Windows Touch 笔势
应用程序开发人员可通过处理来自应用程序的 WndProc 函数的 WM_GESTURE 消息来解释 Windows Touch 笔势。在处理此消息之后,您可以检索描述笔势的GESTUREINFO 结构。GESTUREINFO 结构将具有与笔势类型相关的分类信息。
通过将笔势信息结构的句柄传递给 GetGestureInfo 函数来检索 GESTUREINFO 结构。
以下标志指示笔势的各种状态,这些标志存储在 dwFlags 中。
名称 | 值 | 说明 |
---|---|---|
GF_BEGIN | 0x00000001 | 笔势已开始。 |
GF_INERTIA | 0x00000002 | 笔势已触发延时。 |
GF_END | 0x00000004 | 笔势已完成。 |
注意 大多数应用程序应忽略 GID_BEGIN 和 GID_END,并将它们传递给 DefWindowProc。默认的笔势处理程序将使用这些消息。当第三方应用程序使用 GID_BEGIN 和 GID_END 消息时,应用程序行为将是不确定的。
下表指示笔势的各种标识符。
名称 | 值 | 说明 |
---|---|---|
GID_BEGIN | 1 | 笔势已开始。 |
GID_END | 2 | 笔势已结束。 |
GID_ZOOM | 3 | 缩放笔势。 |
GID_PAN | 4 | 平移笔势。 |
GID_ROTATE | 5 | 旋转笔势。 |
GID_TWOFINGERTAP | 6 | 双指点击笔势。 |
GID_PRESSANDTAP | 7 | 按住并点击笔势。 |
注意 GID_PAN 笔势具有内置延时。在平移笔势结束时,操作系统将创建其他平移笔势消息。
GESTUREINFO 结构成员 ptsLocation 和 ullArguments 指定一个点(使用 POINTS 结构)和有关依赖于该笔势的笔势的其他信息。下表列出了与每类笔势关联的值。
笔势 ID | ullArguments | ptsLocation |
---|---|---|
GID_ZOOM | 指示两个点之间的距离。 | 指示缩放中心。 |
GID_PAN | 指示两个点之间的距离。 | 指示平移的当前位置。 |
GID_ROTATE | 指示旋转角度(如果设置了 GF_BEGIN 标志)。否则,它是自旋转开始后的角度更改。此值将使用正负符号指示旋转的方向。使用 GID_ROTATE_ANGLE_FROM_ARGUMENT 和GID_ROTATE_ANGLE_TO_ARGUMENT 宏获取和设置角度值。 | 这将指示旋转的中心,即目标对象绕其旋转的固定点。 |
GID_TWOFINGERTAP | 指示两个手指之间的距离。 | 指示两个手指的中心。 |
GID_PRESSANDTAP | 指示第一个手指和第二个手指之间的增量。此值将存储在 POINT 结构中的 ullArguments 成员的低 32 位中。 | 指示第一个手指按住的位置。 |
Windows Touch 笔势入门
本节介绍使用多点触控笔势的基本步骤。
以下是使用 Windows Touch 笔势时通常要执行的步骤:
- 设置一个窗口用于接收笔势。
- 处理笔势消息。
- 解释笔势消息。
设置用于接收笔势的窗口
默认情况下,您会收到 WM_GESTURE 消息。
注意 如果调用 RegisterTouchWindow,您将停止接收 WM_GESTURE 消息。如果您未接收到 WM_GESTURE 消息,请确保您尚未调用RegisterTouchWindow。
以下代码显示了简单 InitInstance 实现。
#include <windows.h> BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
处理笔势消息
与处理 Windows Touch 输入消息类似,您可通过很多方式处理笔势消息。如果您正在使用 Win32,则可以检查 WndProc 中的 WM_GESTURE 消息。如果您正在创建其他类型的应用程序,则可以将 WM_GESTURE 消息添加到消息映射中,然后实现一个自定义处理程序。
以下代码显示了可如何处理笔势消息。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message) { case WM_GESTURE: // Insert handler code here to interpret the gesture. return DecodeGesture(hWnd, message, wParam, lParam);
解释笔势消息
GetGestureInfo 函数用于将笔势消息解释为描述笔势的结构。GESTUREINFO 结构包含有关笔势的信息,例如执行笔势的位置和笔势的类型。以下代码显示了如何检索和解释笔势消息。
LRESULT DecodeGesture(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ // Create a structure to populate and retrieve the extra message info. GESTUREINFO gi; ZeroMemory(&gi, sizeof(GESTUREINFO)); gi.cbSize = sizeof(GESTUREINFO); BOOL bResult = GetGestureInfo((HGESTUREINFO)lParam, &gi); BOOL bHandled = FALSE; if (bResult){ // now interpret the gesture switch (gi.dwID){ case GID_ZOOM: // Code for zooming goes here bHandled = TRUE; break; case GID_PAN: // Code for panning goes here bHandled = TRUE; break; case GID_ROTATE: // Code for rotation goes here bHandled = TRUE; break; case GID_TWOFINGERTAP: // Code for two-finger tap goes here bHandled = TRUE; break; case GID_PRESSANDTAP: // Code for roll over goes here bHandled = TRUE; break; default: // A gesture was not recognized break; } }else{ DWORD dwErr = GetLastError(); if (dwErr > 0){ //MessageBoxW(hWnd, L"Error!", L"Could not retrieve a GESTUREINFO structure.", MB_OK); } } if (bHandled){ return 0; }else{ return DefWindowProc(hWnd, message, wParam, lParam); } }
对使用滚动条进行平移的旧版支持
本节介绍了基于 Windows 的应用程序中对使用滚动条进行平移的支持。
在 Windows 7 中,平移笔势会生成 WM_*SCROLL 消息以启用对平移的旧版支持。由于您的应用程序可能不支持所有 WM_*SCROLL 消息,因此平移可能无法正常工作。本主题介绍了为确保应用程序中的旧版平移体验如用户预期那样正常工作所必须执行的步骤。
概述
以下各部分说明如何启用旧版平移体验:
- 创建带滚动条的应用程序。
- 禁用划动。
- 自定义平移体验。
创建带滚动条的应用程序
使用 Microsoft Visual Studio 向导启动新的 Win32 项目。确保将应用程序类型设置为 Windows 应用程序。您无需启用对活动模板库 (ATL) 的支持。在启动项目后,它将如下图所示。
接下来,在图像上启用滚动条。更改 InitInstance 中的窗口创建代码,以便 CreateWindow 函数调用创建一个带滚动条的窗口。以下代码演示如何执行此操作。
hWnd = CreateWindow( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW | WS_VSCROLL, // style 200, // x 200, // y 550, // width 300, // height NULL, NULL, hInstance, NULL );
在更改窗口创建代码之后,应用程序将具有一个滚动条。下图显示了应用程序此时可能具有的外观。
在更改窗口创建代码之后,将滚动条对象添加到应用程序,并添加一些文本以便进行滚动。将以下代码置于 WndProc 方法的顶部。
TEXTMETRIC tm; SCROLLINFO si; // These variables are required to display text. static int xClient; // width of client area static int yClient; // height of client area static int xClientMax; // maximum width of client area static int xChar; // horizontal scrolling unit static int yChar; // vertical scrolling unit static int xUpper; // average width of uppercase letters static int xPos; // current horizontal scrolling position static int yPos; // current vertical scrolling position int i; // loop counter int x, y; // horizontal and vertical coordinates int FirstLine; // first line in the invalidated area int LastLine; // last line in the invalidated area HRESULT hr; int abcLength = 0; // length of an abc[] item int lines = 0; // Create an array of lines to display. static const int LINES=28; static LPCWSTR abc[] = { L"anteater", L"bear", L"cougar", L"dingo", L"elephant", L"falcon", L"gazelle", L"hyena", L"iguana", L"jackal", L"kangaroo", L"llama", L"moose", L"newt", L"octopus", L"penguin", L"quail", L"rat", L"squid", L"tortoise", L"urus", L"vole", L"walrus", L"xylophone", L"yak", L"zebra", L"This line contains words, but no character. Go figure.", L"" };
紧接着,实现应用程序逻辑以便为文本度量配置文本计算。以下代码应替换 WndProc 函数中的现有 WM_CREATE 用例。
case WM_CREATE : // Get the handle to the client area's device context. hdc = GetDC (hWnd); // Extract font dimensions from the text metrics. GetTextMetrics (hdc, &tm); xChar = tm.tmAveCharWidth; xUpper = (tm.tmPitchAndFamily & 1 ? 3 : 2) * xChar/2; yChar = tm.tmHeight + tm.tmExternalLeading; // Free the device context. ReleaseDC (hWnd, hdc); // Set an arbitrary maximum width for client area. // (xClientMax is the sum of the widths of 48 average // lowercase letters and 12 uppercase letters.) xClientMax = 48 * xChar + 12 * xUpper; return 0;
然后,在调整窗口大小时,为文本块的重新计算实现应用程序逻辑。应将以下代码置于 WndProc 中的消息开关中。
case WM_SIZE: // Retrieve the dimensions of the client area. yClient = HIWORD (lParam); xClient = LOWORD (lParam); // Set the vertical scrolling range and page size si.cbSize = sizeof(si); si.fMask = SIF_RANGE | SIF_PAGE; si.nMin = 0; si.nMax = LINES - 1; si.nPage = yClient / yChar; SetScrollInfo(hWnd, SB_VERT, &si, TRUE); // Set the horizontal scrolling range and page size. si.cbSize = sizeof(si); si.fMask = SIF_RANGE | SIF_PAGE; si.nMin = 0; si.nMax = 2 + xClientMax / xChar; si.nPage = xClient / xChar; SetScrollInfo(hWnd, SB_HORZ, &si, TRUE); return 0;
接下来,为垂直滚动消息实现应用程序逻辑。应将以下代码置于 WndProc 中的消息开关中。
case WM_VSCROLL: // Get all the vertical scroll bar information si.cbSize = sizeof (si); si.fMask = SIF_ALL; GetScrollInfo (hWnd, SB_VERT, &si); // Save the position for comparison later on yPos = si.nPos; switch (LOWORD (wParam)) { // user clicked the HOME keyboard key case SB_TOP: si.nPos = si.nMin; break; // user clicked the END keyboard key case SB_BOTTOM: si.nPos = si.nMax; break; // user clicked the top arrow case SB_LINEUP: si.nPos -= 1; break; // user clicked the bottom arrow case SB_LINEDOWN: si.nPos += 1; break; // user clicked the scroll bar shaft above the scroll box case SB_PAGEUP: si.nPos -= si.nPage; break; // user clicked the scroll bar shaft below the scroll box case SB_PAGEDOWN: si.nPos += si.nPage; break; // user dragged the scroll box case SB_THUMBTRACK: si.nPos = si.nTrackPos; break; // user positioned the scroll box // This message is the one used by Windows Touch case SB_THUMBPOSITION: si.nPos = HIWORD(wParam); break; default: break; } // Set the position and then retrieve it. Due to adjustments // by Windows it may not be the same as the value set. si.fMask = SIF_POS; SetScrollInfo (hWnd, SB_VERT, &si, TRUE); GetScrollInfo (hWnd, SB_VERT, &si); // If the position has changed, scroll window and update it if (si.nPos != yPos) { ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL); UpdateWindow (hWnd); } break;
然后,更新代码以重新绘制窗口。以下代码应替换 WndProc 中的默认 WM_PAINT 用例。
case WM_PAINT: // Prepare the window for painting hdc = BeginPaint (hWnd, &ps); // Get vertical scroll bar position si.cbSize = sizeof (si); si.fMask = SIF_POS; GetScrollInfo (hWnd, SB_VERT, &si); yPos = si.nPos; // Get horizontal scroll bar position GetScrollInfo (hWnd, SB_HORZ, &si); xPos = si.nPos; // Find painting limits FirstLine = max (0, yPos + ps.rcPaint.top / yChar); LastLine = min (LINES - 1, yPos + ps.rcPaint.bottom / yChar); for (i = FirstLine; i <= LastLine; i++) { x = xChar * (1 - xPos); y = yChar * (i - yPos); // Note that "55" in the following depends on the // maximum size of an abc[] item. // abcLength = wcslen(abc[i]); hr = S_OK; if ((FAILED(hr))) { MessageBox(hWnd, L"err", L"err", NULL); }else{ TextOut(hdc, x, y, abc[i], abcLength); } } // Indicate that painting is finished EndPaint (hWnd, &ps); return 0;
现在,当生成并运行应用程序时,它应具有样板文本和垂直滚动条。下图显示了应用程序的可能外观。
禁用划动
若要改进应用程序中的平移体验,您应关闭划动。为此,请在初始化 hWnd 值时设置其窗口属性。用于划动的值将存储在 tpcshrd.h 标头中,因此还必须包含该标头。在创建 hWnd 之后,应将以下代码置于包含指令和 InitInstance 函数中。
#include <tpcshrd.h>
[...] BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){ [...]
const DWORD_PTR dwHwndTabletProperty = TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click) gesture TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves) TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button down (circle) TABLET_DISABLE_FLICKS; // disables pen flicks (back, forward, drag down, drag up) SetProp(hWnd, MICROSOFT_TABLETPENSERVICE_PROPERTY, reinterpret_cast<HANDLE>(dwHwndTabletProperty));
自定义平移体验
默认情况下,您可能需要与 Windows 7 提供的平移体验不同的平移体验。若要改进平移体验,您必须为 WM_GESTURE 消息添加处理程序。有关更多信息,请参见改进单指平移体验。
改进单指平移体验
如果生成面向 Windows Touch 的应用程序,则它将自动提供基本的平移支持。但可以使用 WM_GESTURE 消息提供对单指平移的增强支持。
概述
若要改进单指平移体验,请使用以下步骤,如本主题的后续部分中所述:
- 创建带滚动条的应用程序,并禁用划动。
- 添加对笔势平移消息的支持。
- 启用弹跳。
创建带滚动条的应用程序,并禁用划动
在您开始之前,必须创建带滚动条的应用程序。对使用滚动条进行平移的旧版支持部分说明了此过程。如果需要从示例内容开始,请转到该部分,创建带滚动条的应用程序,然后禁用划动。如果已有可正常工作的滚动条的应用程序,则禁用划动,如该部分中所述。
添加对笔势平移消息的自定义平移支持
若要支持笔势平移消息,必须在 WndProc 方法中处理它们。笔势消息用于确定平移消息的水平增量和垂直增量。增量用于更新滚动条对象,这将更新用户界面。
首先,更新 targetver.h 文件中的 Windows 版本设置以启用 Windows Touch。以下代码显示将替换 targetver.h 中的 Windows 版本设置的各种 Windows 版本设置。
#ifndef WINVER // Specifies that the minimum required platform is Windows Vista. #define WINVER 0x0601 // Change this to the appropriate value to target other versions of Windows. #endif #ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista. #define _WIN32_WINNT 0x0601 // Change this to the appropriate value to target other versions of Windows. #endif
紧接着,向项目添加 UXTheme.h 文件,并将 uxtheme.lib 库添加到项目的其他依赖项。
#include <uxtheme.h>
然后,将以下变量添加到 WndProc 函数的顶部。这些变量将用于针对平移的计算。
// The following are used for the custom panning handler BOOL bResult = FALSE; static int scale = 8; // altering the scale value will change how fast the page scrolls static int lastY = 0; // used for panning calculations (initial / previous vertical position) static int lastX = 0; // used for panning calculations (initial / previous horizontal position) GESTUREINFO gi;
接下来,为 WM_GESTURE 消息添加处理程序,以便根据平移笔势用增量更新滚动条。这将使您能够更精细地控制平移。
以下代码从 lParam 获取 GESTUREINFO 结构,保存该结构中的最后一个 y 坐标,并确定位置更改以更新滚动条对象。应将以下代码置于 WndProc switch 语句中。
case WM_GESTURE: // Get all the vertial scroll bar information si.cbSize = sizeof (si); si.fMask = SIF_ALL; GetScrollInfo (hWnd, SB_VERT, &si); yPos = si.nPos; ZeroMemory(&gi, sizeof(GESTUREINFO)); gi.cbSize = sizeof(GESTUREINFO); bResult = GetGestureInfo((HGESTUREINFO)lParam, &gi); if (bResult){ // now interpret the gesture switch (gi.dwID){ case GID_BEGIN: lastY = gi.ptsLocation.y; CloseGestureInfoHandle((HGESTUREINFO)lParam); break; // A CUSTOM PAN HANDLER // COMMENT THIS CASE OUT TO ENABLE DEFAULT HANDLER BEHAVIOR case GID_PAN: si.nPos -= (gi.ptsLocation.y - lastY) / scale; si.fMask = SIF_POS; SetScrollInfo (hWnd, SB_VERT, &si, TRUE); GetScrollInfo (hWnd, SB_VERT, &si); yOverpan -= lastY - gi.ptsLocation.y; lastY = gi.ptsLocation.y; if (gi.dwFlags & GF_BEGIN){ BeginPanningFeedback(hWnd); yOverpan = 0; } else if (gi.dwFlags & GF_END) { EndPanningFeedback(hWnd, TRUE); yOverpan = 0; } if (si.nPos == si.nMin || si.nPos >= (si.nMax - si.nPage)){ // we reached the bottom / top, pan UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA); } ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL); UpdateWindow (hWnd); return DefWindowProc(hWnd, message, lParam, wParam); case GID_ZOOM: // Add Zoom handler return DefWindowProc(hWnd, message, lParam, wParam); default: // You have encountered an unknown gesture return DefWindowProc(hWnd, message, lParam, wParam); } }else{ DWORD dwErr = GetLastError(); if (dwErr > 0){ // something is wrong // 87 indicates that you are probably using a bad // value for the gi.cbSize } } return DefWindowProc (hWnd, message, wParam, lParam);
现在,当您在窗口中执行平移笔势时,您会看到带延时的文本滚动。此时,您可能需要将文本更改为具有多行文本,以便能平移浏览大部分文本。
WndProc 中的边界反馈
边界反馈是在用户到达可平移区域的结尾处时为用户提供的一种可视反馈类型。当到达边界时,应用程序将触发边界反馈。在 WM_GESTURE 消息的上一个示例实现中,来自 (si.nPos == si.yPos)
WM_GESTURE 用例的结束条件用于测试是否已到达可平移区域的结尾处。以下变量用于跟踪值和测试错误。
// The following are used for panning feedback (Window Bounce) static int animCount = 0; static DWORD dwErr = 0; static BOOL isOverpan = FALSE; static long xOverpan = 0; static long yOverpan = 0;
更新平移笔势用例以触发边界反馈。以下代码演示来自 WM_GESTURE 消息处理程序的 GID_PAN 用例。
case GID_PAN: si.nPos -= (gi.ptsLocation.y - lastY) / scale; si.fMask = SIF_POS; SetScrollInfo (hWnd, SB_VERT, &si, TRUE); GetScrollInfo (hWnd, SB_VERT, &si); yOverpan -= lastY - gi.ptsLocation.y; lastY = gi.ptsLocation.y; if (gi.dwFlags & GF_BEGIN){ BeginPanningFeedback(hWnd); yOverpan = 0; } else if (gi.dwFlags & GF_END) { EndPanningFeedback(hWnd, TRUE); yOverpan = 0; } if (si.nPos == si.nMin){ // we reached the top, pan upwards in y direction UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA); }else if (si.nPos >= (si.nMax - si.nPage)){ // we reached the bottom, pan downwards in y direction UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA); } ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL); UpdateWindow (hWnd); return DefWindowProc(hWnd, message, lParam, wParam);
现在,当用户平移通过滚动条区域的底部时,应用程序的窗口应具有边界反馈。