65.9K
CodeProject 正在变化。 阅读更多。
Home

处理长按

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (18投票s)

2003年3月1日

CPOL

3分钟阅读

viewsIcon

227703

关于如何处理长按用户命令的技巧。

引言

本文源于我在 Windows CE 3.0 (PocketPC 2002) 设备上处理长按操作的经验。关于此问题的文档并不总是准确或立即可用。更糟糕的是,MFC 3.0 提供的长按支持存在缺陷。

长按

广义上讲,长按 (TAH) 手势在 Windows CE 应用程序中用作鼠标右键单击的替代品。可以在所有 Microsoft 提供的应用程序中看到这一点,作为调出上下文菜单的一种方式,使其使用非常普遍。当长按操作开始时,用户会从系统收到视觉反馈,即在用户点击屏幕的位置周围以顺时针圆形方式出现许多红色圆圈。

检测和处理

TAH 的检测由 SHRecognizeGesture API 处理。它通常在直接模式或通知模式下用于 WM_LBUTTONDOWN 消息处理程序中。在直接模式下,如果检测到长按命令,该函数返回 GN_CONTEXTMENU,否则返回 0。在通知模式下,它将向父窗口发送带有 GN_CONTEXTMENUWM_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 命令的窗口执行此操作。但是,有两个例外:CListCtrlCTreeCtrl,稍后会详细介绍。

通用处理示例

假设您想在您的 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

© . All rights reserved.