Click here to Skip to main content
15,879,326 members
Articles / Mobile Apps / Windows Mobile

Handling tap-and-hold

Rate me:
Please Sign up or sign in to vote.
4.77/5 (20 votes)
28 Feb 2003CPOL3 min read 220.6K   43   72
Tips on how to handle tap-and-hold user commands.

Introduction

This article results from my experience on handling the tap-and-hold operations on Windows CE 3.0 (PocketPC 2002) devices. Documentation on this issue is not always exact nor immediately available. To make matters worse, the support for tap-and-hold provided by MFC 3.0 is flawed.

Tap-and-hold

Broadly speaking, the tap-and-hold (TAH) gesture is used in Windows CE applications as a replacement for the mouse right-click. One can see this in all Microsot-provided applications as a means to bring up a context menu, making its use quite pervasive. The user receives visual feedback from the system when a tap-and-hold operation begins through a number of red circles showing up in a clockwise circular fashion around the place where the user tapped the screen.

Detection and Handling

Detection of a TAH is handled by the SHRecognizeGesture API. It is generally used in the WM_LBUTTONDOWN message handler in either direct mode or notification mode. In direct mode the function returns GN_CONTEXTMENU if it detected a tap-and-hold command, or 0 otherwise. In notification mode, it will send a WM_NOTIFY message with a GN_CONTEXTMENU to the parent window.

Don't Trust MFC

MFC's handling is done in CWnd::OnLButtonDown and, unfortunately, is flawed. If you look in wincore.cpp you see how it is implemented:

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();
}

That's why all MFC windows respond to TAH commands twice, not once. Worse, it makes all of your windows respond to TAH. As a matter of fact, you may not want this to happen. The solution is quite simple: make sure your OnLButtonDown does not call CWnd's but Default() instead:

void CMyWnd::OnLButtonDown(UINT nState, CPoint point)
{
    Default();
}

Unfortunately, you will have to do this for all the windows that you don't want to handle the TAH command. There are two exceptions, though: CListCtrl and CTreeCtrl, but more on these later.

Generic Handling Example

Let's assume you want to handle TAH on your CWnd-derived window class. One option is:

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();
}

This is the direct mode, where SHRecognizeGesture returns a value that notifies if a TAH happened. Note that I'm using the API version of the method, not CWnd's. If a TAH is detected, the ContextMenu method is called. Here's how it might be implemented:

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);
        }
    }
}

In this class, m_nMenuID is a UINT that holds the context menu's ID. If NULL, no menu is shown. The m_pWndMenu member variable holds a pointer to the command-processing window. This is useful if you want to show TAH context menus on controls placed in dialogs. In these situations you cannot use AfxGetMainWnd() because your menu commands might be either grayed, or worse, handled by another (hidden) window. So, m_pWndMenu will have the containing CDialog pointer.

The same code may be used in notification mode (see next section).

Note: The way OnLButtonDown was implemented is not mandatory. In some situations you may have to always call Default(), depending on the underlying window functionality.

CListCtrl and CTreeCtrl

These are two different beasts, because they do implement the correct TAH functionality at the system level. Unfortunately, MFC's encapsulation destroys it. So, unlike other windows, if you want these two to have a correct TAH behaviour, call Default() on OnLButtonDown. Intuitive, right? These controls' TAH implementation use the notification method, so there are a couple of other things to do. Here is a sample for a CListCtrl-derived class:

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;
}

The concept is similar, with two exceptions. First, we store the TAH status in the boolean m_bTapAndHold variable. Second, we store the clicked item on the integer m_iItemOnMenu.

The first may be use when processing NM_CLICK notifications. In these notifications it is impossible to know if the user simply clicked the control, or if she is issuing a TAH command. By testing the m_bTapAndHold we can implement the same behaviour of the Contacts application, where a single click means edit, and a TAH means context menu.

The second variable may be used by the ON_UPDATE_COMMAND_UI handles to determine what menu options are available. Remember that m_iItemOnMenu will be -1 if the user tapped outside the items area.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Frotcom International
Portugal Portugal
I work on R&D for Frotcom International, a company that develops web-based fleet management solutions.

Comments and Discussions

 
GeneralHelp me Pin
An Phung Nguyen22-Jul-05 14:48
An Phung Nguyen22-Jul-05 14:48 
GeneralRe: Help me Pin
João Paulo Figueira20-Aug-05 0:16
professionalJoão Paulo Figueira20-Aug-05 0:16 
GeneralMenu pops up 3 times...! Pin
Jor-El24-Feb-05 2:41
Jor-El24-Feb-05 2:41 
GeneralRe: Menu pops up 3 times...! Pin
João Paulo Figueira24-Feb-05 2:50
professionalJoão Paulo Figueira24-Feb-05 2:50 
GeneralRe: Menu pops up 3 times...! Pin
Jor-El24-Feb-05 3:30
Jor-El24-Feb-05 3:30 
GeneralAre you wrong or am I missing something? TRUST MFC Pin
jweldin8-Jul-04 15:27
jweldin8-Jul-04 15:27 
GeneralRe: Are you wrong or am I missing something? TRUST MFC Pin
João Paulo Figueira8-Jul-04 22:43
professionalJoão Paulo Figueira8-Jul-04 22:43 
GeneralRe: Are you wrong or am I missing something? TRUST MFC Pin
jweldin16-Jul-04 18:37
jweldin16-Jul-04 18:37 
After stepping through the code I see what you mean. Excellent workaround! Smile | :)
GeneralProblem with Handling TAP and HOLD Pin
Member 251592128-Feb-04 2:05
Member 251592128-Feb-04 2:05 
GeneralRe: Problem with Handling TAP and HOLD Pin
João Paulo Figueira28-Feb-04 5:55
professionalJoão Paulo Figueira28-Feb-04 5:55 
GeneralProblem After Menu Selection Pin
Member 42211227-Nov-03 11:28
Member 42211227-Nov-03 11:28 
GeneralRe: Problem After Menu Selection Pin
João Paulo Figueira30-Nov-03 6:00
professionalJoão Paulo Figueira30-Nov-03 6:00 
GeneralRe: Problem After Menu Selection Pin
Member 4221127-Dec-03 2:47
Member 4221127-Dec-03 2:47 
GeneralRe: Problem After Menu Selection Pin
João Paulo Figueira7-Dec-03 4:12
professionalJoão Paulo Figueira7-Dec-03 4:12 
GeneralRe: Problem After Menu Selection Pin
Member 4221128-Dec-03 0:47
Member 4221128-Dec-03 0:47 
GeneralPopup menu processing Pin
Lymeric22-Oct-03 16:44
Lymeric22-Oct-03 16:44 
GeneralRe: Popup menu processing Pin
João Paulo Figueira22-Oct-03 22:44
professionalJoão Paulo Figueira22-Oct-03 22:44 
GeneralRe: Popup menu processing Pin
Lymeric23-Oct-03 10:03
Lymeric23-Oct-03 10:03 
GeneralRe: Popup menu processing Pin
Lymeric23-Oct-03 10:50
Lymeric23-Oct-03 10:50 
QuestionHow can I disable the red circles? Pin
Rafael Leonhardt10-Oct-03 10:16
Rafael Leonhardt10-Oct-03 10:16 
AnswerRe: How can I disable the red circles? Pin
Rafael Leonhardt10-Oct-03 10:24
Rafael Leonhardt10-Oct-03 10:24 
GeneralPlain Windows CE 3.0 Pin
Firas1-Jun-03 20:39
Firas1-Jun-03 20:39 
GeneralRe: Plain Windows CE 3.0 Pin
João Paulo Figueira1-Jun-03 22:11
professionalJoão Paulo Figueira1-Jun-03 22:11 
GeneralHelp.. Help.. Pin
newbie7928-May-03 5:33
newbie7928-May-03 5:33 
GeneralRe: Help.. Help.. Pin
João Paulo Figueira1-Jun-03 22:07
professionalJoão Paulo Figueira1-Jun-03 22:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.