处理长按






4.77/5 (18投票s)
关于如何处理长按用户命令的技巧。
引言
本文源于我在 Windows CE 3.0 (PocketPC 2002) 设备上处理长按操作的经验。关于此问题的文档并不总是准确或立即可用。更糟糕的是,MFC 3.0 提供的长按支持存在缺陷。
长按
广义上讲,长按 (TAH) 手势在 Windows CE 应用程序中用作鼠标右键单击的替代品。可以在所有 Microsoft 提供的应用程序中看到这一点,作为调出上下文菜单的一种方式,使其使用非常普遍。当长按操作开始时,用户会从系统收到视觉反馈,即在用户点击屏幕的位置周围以顺时针圆形方式出现许多红色圆圈。
检测和处理
TAH 的检测由 SHRecognizeGesture
API 处理。它通常在直接模式或通知模式下用于 WM_LBUTTONDOWN
消息处理程序中。在直接模式下,如果检测到长按命令,该函数返回 GN_CONTEXTMENU
,否则返回 0
。在通知模式下,它将向父窗口发送带有 GN_CONTEXTMENU
的 WM_NOTIFY
消息。
不要信任 MFC
MFC 的处理在 CWnd::OnLButtonDown
中完成,但不幸的是,它存在缺陷。如果您查看 wincore.cpp
,您会看到它是如何实现的
BOOL CWnd::SHRecognizeGesture(CPoint point, BOOL bSendNotification /* = TRUE */) { SHRGINFO shrgi = {0}; shrgi.cbSize = sizeof(SHRGINFO); shrgi.hwndClient = m_hWnd; shrgi.ptDown.x = point.x; shrgi.ptDown.y = point.y; shrgi.dwFlags = SHRG_RETURNCMD; if(GN_CONTEXTMENU == ::SHRecognizeGesture(&shrgi)) { if(bSendNotification) { shrgi.dwFlags = SHRG_NOTIFYPARENT; ::SHRecognizeGesture(&shrgi); // Again??? } return TRUE; } else return FALSE; } void CWnd::OnLButtonDown (UINT nState, CPoint point) { if (!SHRecognizeGesture(point)) Default(); }
这就是为什么所有 MFC 窗口都会响应 TAH 命令**两次**,而不是一次。更糟糕的是,它使**所有**您的窗口都响应 TAH。事实上,您可能不希望这种情况发生。解决方案很简单:确保您的 OnLButtonDown
不调用 CWnd
的,而是调用 Default()
void CMyWnd::OnLButtonDown(UINT nState, CPoint point) { Default(); }
不幸的是,您必须对所有不想处理 TAH 命令的窗口执行此操作。但是,有两个例外:CListCtrl
和 CTreeCtrl
,稍后会详细介绍。
通用处理示例
假设您想在您的 CWnd
派生窗口类上处理 TAH。一种选择是
void CMyWnd::OnLButtonDown(UINT nFlags, CPoint point) { SHRGINFO shrgi = {0}; shrgi.cbSize = sizeof(SHRGINFO); shrgi.hwndClient = m_hWnd; shrgi.ptDown.x = point.x; shrgi.ptDown.y = point.y; shrgi.dwFlags = SHRG_RETURNCMD; if(GN_CONTEXTMENU == ::SHRecognizeGesture(&shrgi)) ContextMenu(point); else Default(); }
这是直接模式,其中 SHRecognizeGesture
返回一个值,指示是否发生了 TAH。请注意,我使用的是该方法的 API 版本,而不是 CWnd
的。如果检测到 TAH,则调用 ContextMenu
方法。以下是它的实现方式
void CMyCwnd::ContextMenu(CPoint point) { CMenu mnuCtxt; CMenu* pMenu; CWnd* pWnd; if(!m_nMenuID) return; if(mnuCtxt.LoadMenu(m_nMenuID)) { pWnd = (m_pWndMenu ? m_pWndMenu : AfxGetMainWnd()); pMenu = mnuCtxt.GetSubMenu(0); if(pMenu) { ClientToScreen(&point);pMenu->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, pWnd); } } }
在此类中,m_nMenuID
是一个 UINT
,它保存上下文菜单的 ID。如果为 NULL
,则不显示菜单。m_pWndMenu
成员变量保存指向命令处理窗口的指针。如果您想在对话框中放置的控件上显示 TAH 上下文菜单,这将非常有用。在这些情况下,您不能使用 AfxGetMainWnd()
,因为您的菜单命令可能会变灰,或者更糟糕的是,由另一个(隐藏的)窗口处理。因此,m_pWndMenu
将具有包含的 CDialog
指针。
相同的代码可以用于通知模式(参见下一节)。
注意:OnLButtonDown
的实现方式不是强制性的。在某些情况下,您可能必须始终调用 Default()
,具体取决于底层窗口功能。
CListCtrl 和 CTreeCtrl
这是两个不同的东西,因为它们在系统级别实现了正确的 TAH 功能。不幸的是,MFC 的封装破坏了它。因此,与其他窗口不同,如果您希望这两个窗口具有正确的 TAH 行为,请在 OnLButtonDown
上调用 Default()
。直观,对吧?这些控件的 TAH 实现使用通知方法,因此还有一些其他事情要做。以下是 CListCtrl
派生类的示例
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl) //{{AFX_MSG_MAP(CMyListCtrl) ON_WM_LBUTTONDOWN () ON_WM_LBUTTONUP () ON_NOTIFY_REFLECT (GN_CONTEXTMENU, OnListContextMenu) . . . //}}AFX_MSG_MAP END_MESSAGE_MAP() // CMyListCtrl::OnListContextMenu // // Handles the list context menu // void CMyListCtrl::OnListContextMenu(NMHDR* pNMHDR, LRESULT* pResult) { CMenu mnuCtxt; CMenu* pMenu; CWnd* pWnd; NMRGINFO* pInfo; if(!m_nLstMenu) return; if(mnuCtxt.LoadMenu(m_nLstMenu)) { pWnd = (m_pWndMenu ? m_pWndMenu : AfxGetMainWnd()); pMenu = mnuCtxt.GetSubMenu(0); if(pMenu) { UINT uFlags; CPoint pt; pInfo = (NMRGINFO*)pNMHDR; pt = pInfo->ptAction; ScreenToClient(&pt); m_iItemOnMenu = HitTest(pt, &uFlags); // // Signal this is a tap and hold operation // m_bTapAndHold = RUE; pMenu->TrackPopupMenu(TPM_LEFTALIGN, pInfo->ptAction.x,pInfo->ptAction.y, pWnd); } } } // CMyListCtrl::OnLButtonDown // // Special handler for WM_LBUTTONDOWN. // Check for a tap and hold gesture. Never trust MFC... // void CMyListCtrl::OnLButtonDown(UINT nFlags, CPoint point) { m_bTapAndHold = FALSE; Default(); } // CMyListCtrl::OnLButtonUp // // The user released the stylus // void CMyListCtrl::OnLButtonUp(UINT nFlags, CPoint point) { CListCtrl::OnLButtonUp(nFlags, point); m_bTapAndHold = FALSE; }
这个概念类似,但有两个例外。首先,我们将 TAH 状态存储在布尔变量 m_bTapAndHold
中。其次,我们将单击的项目存储在整数 m_iItemOnMenu
中。
第一个可以在处理 NM_CLICK
通知时使用。在这些通知中,不可能知道用户只是简单地单击了控件,还是发出了 TAH 命令。通过测试 m_bTapAndHold
,我们可以实现与联系人应用程序相同的行为,其中单击一次表示编辑,TAH 表示上下文菜单。
第二个变量可以被 ON_UPDATE_COMMAND_UI
句柄使用,以确定哪些菜单选项可用。请记住,如果用户在项目区域外点击,则 m_iItemOnMenu
将为 -1
。