概述
Windows 7 支持用户通过手指接触来管理应用程序,无需使用中间设备。这扩展了平板 PC 基于触笔的功能。与其他指针设备不同,这种新功能允许多个输入事件在不同指针位置同时发生,它还支持复杂的场景,比如通过十个手指或多个并发用户来管理应用程序。但是,要实现这些功能,我们必须调整应用程序的用户界面和行为,以支持这种新的输入模型。
Visual Studio 2010 的 MFC 增加了检查多点触控硬件就绪情况的功能,还简化了接收触控事件的流程。
目标
在本次动手实验中,您将学习如何管理多点触控事件,包括:
• 处理 Windows Touch 的输入
• 理解同时操作多个触控事件的含义
• 查看多点触控硬件是否存在及其就绪情况
系统要求
您必须拥有以下工具才能完成本实验:
• Windows 7
• Microsoft Visual Studio 2010 Beta 2(或更高版本)
• 多点触控硬件设备
简介
要创建一个多点触控驱动的应用程序,您可以选择以下 3 种方法之一:“好”、“出色”和“最佳”。
“好” 方法是这三种方法中最简单的一种。您应该将触控功能设计到应用程序的用户界面中。使用较大而整洁的基于 Win32 的控件来保证界面的自然,提供更佳的用户体验。滚动之类的触控功能来自 Win32 控件。不需要其他工作。例如,尝试使用手指滚动您现在正在阅读的文档!这是一个“好”方法。
“出色”方法允许系统接收各种低级触控事件,并将系统执行这些事件的启发结果作为“手势”传递给您的应用程序。例如,如果用户在屏幕上进行旋转移动,系统将根据旋转角度发出一个旋转手势事件。尽管“出色”方法很容易使用,但它也存在不足。使用笔势,我们不可能同时进行旋转、平移和缩放。您也不能同时处理多个基于触控的不同动作。例如,两名用户操作窗口的不同区域。
“最佳”方法是读取低级触控事件作为应用程序的输入。“Piano”或多滑块之类可以同时操作的复杂控件就是两个很好的例子。例如,运行 MS Paint,从库中选择绘制工具并使用四个手指进行绘制:
在本手动实验中,您将使用“最佳”方法模拟新的 MS Paint 多点触控绘图功能。我们将读取并使用原始触控事件。
关于 Multitouch Scratchpad 应用程序
Multitouch Scratchpad 应用程序展示了一个简单的窗口,它允许使用手指同时绘制线条。文件夹 Source/MFC_WMTouchSource/Starter 包含练习所需的文件,Source/MFC_WMTouchSource/Final 包含完成的解决方案。
练习 1:构建多点触控应用程序
任务 1 –准备应用程序
1. 启动 Visual Studio 2010
2. 新建一个 MFC 应用程序项目,并将其命名为 ScratchPad:
3. 在 Application Type 中选择 Single Document。为了保持应用程序简单,不选择对话框(如以下屏幕截图所示)中的其他选项:
4. 继续单击 Next 直到单击 Finish:
任务 2 –向应用程序添加触控支持
1. 我们正在构建的应用程序需要支持触控的硬件,因此我们需要在应用程序中查看这一点。
2. 在 Scratchpad.cpp 中,在 CScratchPadApp::InitInstance(): 后添加以下检查代码:
BYTE digitizerStatus = (BYTE) GetSystemMetrics(SM_DIGITIZER);
if ((digitizerStatus & (0x80 + 0x40)) == 0) //Stack Ready + MultiTouch
{
AfxMessageBox(L"No touch input is currently available.");
return false;
}
BYTE nInputs = (BYTE) GetSystemMetrics(SM_MAXIMUMTOUCHES);
CString str;
str.Format(L"Touch input available with %d touch points.", nInputs);
AfxMessageBox(str);
return true;
4. 编译并运行。
5. 根据机器上触控输入的数量,您应该看到类似下图的输出:
6. 为了注册应用程序客户端视图窗口来接收触控消息,我们需要调用 MFC 函数 CWnd::RegisterTouchWindow()。我们将在视图创建之后执行该操作,即在 OnCreate() 事件处理程序中完成。
切换到 Class View 并选择 CChildView 类。
在 Properties 页面中,转到 Message 属性表并导航到 WM_CREATE,然后从下拉框中添加 OnCreate() 消息处理程序:
7. 在 CChildView::OnCreate() 处理程序中添加以下代码,注册视图窗口的触控输入:
if (!RegisterTouchWindow()) { ASSERT(FALSE); }
8. 因为我们注册了视图来接收触控输入,我们必须重写接收每个触控消息的处理程序:CWnd::OnTouchInput()。
该处理程序接收来自 Windows Touch 的单个输入,并在应用程序处理该消息时返回 TRUE;否则返回 FALSE。
9. 在 ChildView.h 中添加该方法声明:
// Overrides protected: virtual BOOL OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput);
10. 在 ChildView.cpp 中提供相应的实现:
BOOL CChildView::OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput) { // TODO:Handle Tocuh input messages here return false; }
任务 3 –向项目添加笔画源和头文件,并使用手指绘制线条
我们希望将手指作为多输入设备。我们希望为每个触摸屏幕的手指绘制一条线。为此,我们将使用两个笔画集合。一个集合保存完成的笔画(线条),另一个集合保存正在绘制的线条。触摸屏幕的每个手指都指向 m_StrkColDrawing 集合中的笔画。当我们从屏幕拿开手指时,我们将手指的笔画从m_StrkColDrawing 移动到 m_StrkColFinished 集合。此外,如果用户在多点触控监视器上使用两个以上的手指,我们希望笔画有不同的颜色。
1. 在 Starter 文件夹中,您将找到两个文件:Stroke.h 和 Stroke.cpp。将它们复制到项目文件夹下并使用“Add Existing item…”将其添加到项目中。
2. 类似地,向项目添加 StrokeCollection.h 和 StrokeCollection.cpp。
3. 将 "Stroke.h" 和 "StrokeCollection.h" 放在 StdAfx.h 头文件结束处。
#include "Stroke.h" #include "StrokeCollection.h"
private: int m_iCurrColor; // The current stroke color CStrokeCollection m_StrkColFinished; // The user finished entering strokes // after user lifted his or her finger. CStrokeCollection m_StrkColDrawing; // The Strokes collection the user is // currently drawing.
CChildView::CChildView() :m_iCurrColor(0) { }
m_StrkColFinished.Draw(&dc);
8. 在 CChildView.h 中,声明以下方法,我们将用来处理不同的触控输入事件:
protected: // Handlers for different touch input events BOOL OnTouchInputDown(CPoint pt, PTOUCHINPUT pInput); BOOL OnTouchInputMove(CPoint pt, PTOUCHINPUT pInput); BOOL OnTouchInputUp(CPoint pt, PTOUCHINPUT pInput);
BOOL CChildView::OnTouchInputDown(CPoint pt, PTOUCHINPUT pInput) { // Create new stroke and add point to it. COLORREF strokeColor = GetTouchColor((pInput->dwFlags & TOUCHEVENTF_PRIMARY) != 0); CStroke* pStrkNew = new CStroke(pInput->dwID, strokeColor); pStrkNew->Add(pt); // Add new stroke to the collection of strokes in drawing. m_StrkColDrawing.Add(pStrkNew); return true; } BOOL CChildView::OnTouchInputMove(CPoint pt, PTOUCHINPUT pInput) { // Find the stroke in the collection of the strokes in drawing. int strokeIndex = m_StrkColDrawing.FindStrokeById(pInput->dwID); if (strokeIndex >= 0) { CStroke* pStrk = m_StrkColDrawing[strokeIndex]; // Add contact point to the stroke pStrk->Add(pt); // Draw the last stroke pStrk->Draw(GetDC()); } return true; } BOOL CChildView::OnTouchInputUp(CPoint pt, PTOUCHINPUT pInput) { // Find the stroke in the collection of the strokes in drawing. int strokeIndex = m_StrkColDrawing.FindStrokeById(pInput->dwID); if (strokeIndex >= 0) { CStroke* pStrkCopy = m_StrkColDrawing[strokeIndex]; // Remove this stroke from the collection of strokes in drawing. m_StrkColDrawing.RemoveAt(strokeIndex); // Add this stroke to the collection of finished strokes. m_StrkColFinished.Add(pStrkCopy); } return true; }
BOOL CChildView::OnTouchInput(CPoint pt, int nInputNumber, int nInputsCount, PTOUCHINPUT pInput) { if ((pInput->dwFlags & TOUCHEVENTF_DOWN) == TOUCHEVENTF_DOWN) // Touch Down event { return OnTouchInputDown(pt, pInput); } else if ((pInput->dwFlags & TOUCHEVENTF_MOVE) == TOUCHEVENTF_MOVE) // Touch Move event { return OnTouchInputMove(pt, pInput); } else if ((pInput->dwFlags & TOUCHEVENTF_UP) == TOUCHEVENTF_UP) // Touch Move event { return OnTouchInputUp(pt, pInput); } return false; }
private: COLORREF GetTouchColor(bool bPrimaryContact);
COLORREF CChildView::GetTouchColor(bool bPrimaryContact) { static COLORREF c_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 primary contact is drawn in black. color = RGB(0,0,0); // Black } else { // Take current secondary color. color = c_arrColor[m_iCurrColor]; // Move to the next color in the array. m_iCurrColor = (m_iCurrColor + 1) % (sizeof(c_arrColor)/sizeof(c_arrColor[0])); } return color; }
CChildView::~CChildView() { for (int i = 0; i < m_StrkColDrawing.GetCount(); ++i) { delete m_StrkColDrawing[i]; } for (int i = 0; i < m_StrkColFinished.GetCount(); ++i) { delete m_StrkColFinished[i]; } }
15. 编译并运行应用程序。它应该如下所示:
总结
在本实验中,您学习了如何在 MFC 应用程序中处理触控输入。您学习了如何测试多点触控硬件是否存在,以及如何配置窗口来接收触控输入。同样,您还看到了如何从消息中提取输入,以及系统如何关联触控 id 与触控输入。