This is one of the more obscure posts I have ever written, but if you need to do this I think this is the only post on the web with an end to end walkthrough of setting up a reasonably user friendly Win32 TreeView right click menu. The great thing about C and C++ is they are very granular. That is also often the reason they are challenging. Combined, these properties make writing programs more rewarding than building model ships inside opaque bottles. Today’s adventure is one of those times. In higher level languages most of a context menu is just walking through some predefined steps adding your particular content and some minor behavioral customization. This isn’t that. Right clicking a Win32 TreeView item has the default behavior of highlighting the clicked item then bouncing back to the previously selected item when the button is released. It gets better. In order to complete my task of taming in a context menu I have to do the following:

  • Get the TreeView item selected using either the left or right mouse button
  • Detect if the mouse is over either the newly selected item or over the already selected item
  • Fire the menu

In order to pull this off I decided to catch and handle the TV handle then work with the NM_RCLICK message since I have to work with several scenarios including forcing a change of selected item. To summarize I change the selection if required, check the mouse is still over the selected item to avoid mystery menus away from selections and fire the menu. Here is the code that does that.

// These 2 vars are in file scope
POINT mousePos;
HWND Tv;

	case WM_NOTIFY:
	{
		switch (LOWORD(wParam))
		{
		case NETW_TV:

			LPNMHDR pNMHDR = (LPNMHDR)lParam;
				
			if (pNMHDR->code == NM_RCLICK)
			{
				BOOL validClick = FALSE;

				HTREEITEM hItem = TreeView_GetNextItem(pNMHDR->hwndFrom, 0, TVGN_DROPHILITE);
				if (hItem)
				{
					TreeView_SelectItem(pNMHDR->hwndFrom, hItem);
				}
					
				// Check the location of the click against the tv selected item
				HTREEITEM hSelItem = TreeView_GetSelection(Tv);
				if (hSelItem)
				{
					RECT tvRect; // The tv window coordinates relative to the client area
					GetWindowRect(Tv, &tvRect);

					RECT nodeRect; // The TREEITEM coordinates relative to the tv
					TreeView_GetItemRect(Tv, hSelItem, &nodeRect, 0); // Change to 1 to restrict to rect around text only

					if (nodeRect.right && tvRect.right) // We have rectangles
					{
						GetCursorPos(&mousePos);

						// Adding the position of the tv in the client area to match the mouse coordinates
						if ((mousePos.x >= (nodeRect.left + tvRect.left) && mousePos.x <= (nodeRect.right + tvRect.left)) &&
							(mousePos.y >= (nodeRect.top + tvRect.top) && mousePos.y <= (nodeRect.bottom + tvRect.top)))
						{
							validClick = TRUE;
						}
					}
				}

				if (validClick)
				{
					// A test menu
					HMENU ctext = CreatePopupMenu();
					AppendMenu(ctext, MF_STRING, 2, L"item");
					TrackPopupMenu(ctext, 0, mousePos.x, mousePos.y, 0, hWnd, 0);
				}
					
			}
		}
	}
	break;