I’m simply trying to add small color swatches to my context menu (displayed via the TrackPopupMenu API.) Here’s a Photoshopped version of what I’m trying to achieve:
As far as I understand the default menu does not support it. Btw, the sample above (without color swatches) was generated by doing this:
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING;
mii.fType = MFT_STRING;
mii.wID = ID_1_MARKER_01 + m;
mii.dwTypeData = L"Marker";
mii.cch = TSIZEOF(L"Marker");
mii.fState = m == 1 ? MFS_CHECKED : MFS_ENABLED;
if(m == 2)
mii.fState |= MFS_GRAYED;
VERIFY(::InsertMenuItem(hMenu, ID_1_BEFORE, FALSE, &mii));
So I discovered that I need to use MFT_OWNERDRAW
style to draw menu items myself, but that’s where the problems begin.
I changed my code to display my menu as such:
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE;
mii.fType = MFT_OWNERDRAW;
mii.wID = ID_1_MARKER_01 + m;
mii.dwItemData = MARKER_ID_01 + m;
mii.fState = m == 1 ? MFS_CHECKED : MFS_ENABLED;
if(m == 2)
mii.fState |= MFS_GRAYED;
VERIFY(::InsertMenuItem(hMenu, ID_1_BEFORE, FALSE, &mii));
then I needed to override WM_MEASUREITEM
and WM_DRAWITEM
messages. But when I do it with the code that I’ll show below, here’s what I get:
So please bear with me. I have several questions on this topic:
1) While processing WM_MEASUREITEM
how am I supposed to know the size of text if they do not supply neither DC
nor HWND
for the menu? In other words, if I do this, the size of the menus is wrong:
#define TSIZEOF(f) ((sizeof(f) - sizeof(TCHAR)) / sizeof(TCHAR))
//hwnd = HWND supplied in WM_MEASUREITEM notification
HDC hDC = ::GetDC(hwnd);
HGDIOBJ hOldFont = ::SelectObject(hDC, ::SendMessage(hwnd, WM_GETFONT, 0, 0));
SIZE szTxt = {0};
::GetTextExtentPoint32(hDC,
L"Marker",
TSIZEOF(L"Marker"),
&szTxt);
//lpmis = MEASUREITEMSTRUCT*
lpmis->itemWidth = szTxt.cx;
lpmis->itemHeight = szTxt.cy;
::SelectObject(hDC, hOldFont);
::ReleaseDC(hwnd, hDC);
2) Then while processing WM_DRAWITEM
how do I know the offset to begin drawing text on the left? If I do this, my menus aren’t offset enough to the right (as you can see from the screenshot above):
int nCheckW = ::GetSystemMetrics(SM_CXMENUCHECK);
//lpdis = DRAWITEMSTRUCT*
::ExtTextOut(lpdis->hDC,
lpdis->rcItem.left + nCheckW,
lpdis->rcItem.top,
ETO_OPAQUE,
&lpdis->rcItem,
L"Marker",
TSIZEOF(L"Marker"),
NULL);
3) And lastly how do I draw that default checkbox on the left of the menu item?
2
Answers
While I don’t use color swatches, and, use strictly MFC, I do render bitmaps on my derived menu items. You should be able to adapt the following for your needs.
In measuring the item text, I use the desktop dc.
Along with some tiny adjustments through experimentation, I came up with what I needed for the size.
To render the actual bitmap and text, I check to see if a theme is active and render the menu item in one of two different ways. Either as a themed menu item or a standard menu item. To render the text, I start with the rect that was passed via the LPDRAWITEMSTRUCT. I then make the following adjustment before rendering the text.
Through trial and error, I found that BITMAP_ADJUSTMENT set to 30 worked for me. Then, depending on the stated of the item (disabled, selected), the rectt is adjusted further.
Lastly, to render the checkmark, I create an image list using a pre-defined bitmap (I may have extracted it from a Microsoft dll). Again, the bitmap is rendered according to the state of the item.
Most of the rendering was accomplished through an iterative approach adjusting the rect objects after each try.
Alternative way with color icons.
Windows Vista+:
Windows XP: