
| Last Modification: December 11, 1999 |
Why don't I get a WM_CONTEXTMENU message when I right click in a Tree Control?
The tree control enters its own modal message loop when you click an item.
This modal loop processes the WM_RBUTTONUP message itself and doesn't pass
it to the default handler (which would then generate the WM_CONTEXTMENU
message).
A quick and dirty solution is to handle the WM_RBUTTONDOWN message and not
call the default handler.
A better but more involved solution is to handle the WM_NOTIFY NM_RCLICK
message instead. If you handle this and defer it to your WM_CONTEXTMENU
message handler, you'll get correct mouse and keyboard (Shift+F10, or the
context menu key on expanded keyboards) operation.
Here's an MFC example which illustrates setting a reasonable popup position for keyboard activation (lots of programs don't bother with this, with the result that keyboard activated popup menus don't appear where you'd expect). The example also illustrates a technique to simulate what Explorer does when you right click an item that's not currently selected.
/* This class member variable is used to allow the right
* click operation to temporarily switch the selected item.
* Make it a private class member variable of your derived
* Tree control class
*/
HTREEITEM m_hTempSelect;
void CMyTreeView::OnRclick(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
CTreeCtrl & tc = GetTreeCtrl();
/* Get the mouse cursor position */
DWORD dwPos = GetMessagePos();
/* Convert the co-ords into a CPoint structure */
CPoint pt( GET_X_LPARAM( dwPos ), GET_Y_LPARAM( dwPos ) ), spt;
spt = pt;
/* Convert to screen co-ords for hittesting */
tc.ScreenToClient( &spt );
UINT test;
HTREEITEM hti = tc.HitTest( spt, &test );
/* Did the click occur on an item */
if ( ( hti != NULL ) && ( test & TVHT_ONITEM ) )
{
HTREEITEM htCur = tc.GetSelectedItem();
/* Store the value of the right clicked item only *if*
* it's different from the currently selected item.
*/
if ( hti != htCur )
{
m_hTempSelect = hti;
}
/* Do the context menu */
OnContextMenu( this, pt );
}
*pResult = 0;
}
void CMyTreeView::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
CTreeCtrl & tc = GetTreeCtrl();
CMenu menu;
menu.LoadMenu( IDR_TREE_CONTEXT_MENU );
CMenu * pop;
pop = menu.GetSubMenu(0);
/* If activated by the keyboard, get
* the position of the selected item
*/
if ( point.x == -1 )
{
HTREEITEM ht = tc.GetSelectedItem();
RECT rect;
tc.GetItemRect( ht, &rect, true );
tc.ClientToScreen( &rect );
/* Offset the popup menu origin so
* we can read some of the text
*/
point.x = rect.left + 15;
point.y = rect.top + 8;
}
/* To ensure that commands and menu update handling is done
* properly, make the parent of the menu the main frame window
*/
CWnd * pMenuParent = AfxGetMainWnd();
UINT uCmd = pop->TrackPopupMenu( TPM_RETURNCMD | TPM_LEFTALIGN |
TPM_RIGHTBUTTON, point.x, point.y,
pMenuParent, NULL );
/* Menu item chosen ? */
if ( uCmd != 0 )
{
/* Temporarily select any right-clicked item that's
* different from the currently selected item
*/
if ( m_hTempSelect )
{
HTREEITEM hOldSel = tc.GetSelectedItem();
tc.Select( m_hTempSelect, TVGN_CARET );
/* Execute the selected menu command */
pMenuParent->SendMessage( WM_COMMAND, uCmd, 0 );
tc.Select( hOldSel, TVGN_CARET );
}
else
{
/* Execute the selected menu command */
pMenuParent->SendMessage( WM_COMMAND, uCmd, 0 );
}
}
/* No longer need the temporary value */
m_hTempSelect = NULL;
menu.DestroyMenu();
}