How to correctly measure and display the owner context menu item using a checkmark?

I'm just 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:

enter image description here

As far as I understand, the default menu does not support it. Btw, the sample above (without color samples) was generated as follows:

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 found that I need to use the MFT_OWNERDRAW style to draw menu items myself, but where problems arise.

I modified 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 the WM_MEASUREITEM and WM_DRAWITEM messages. But when I do this with the code that I will show below, this is what I get:

enter image description here

So please bear with me I have a few questions on this topic:

1) During WM_MEASUREITEM processing, how should I know the size of the text if they provide neither DC nor HWND for the menu? In other words, if I do this, the menu size is incorrect:

 #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, when processing WM_DRAWITEM how do I know the offset to start drawing text on the left? If I do this, my menus will not be sufficiently shifted to the right (as you can see in 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 finally, how can I check this box by default on the left side of the menu item?

+5
source share
2 answers

An alternative way with colored icons.

Windows Vista +:

enter image description here

Windows XP:

enter image description here

 function GetOptimalCheckColor(AColor: COLORREF): COLORREF; var Gray: Integer; begin Gray := Round((0.30 * GetRValue(AColor)) + (0.59 * GetGValue(AColor)) + (0.11 * GetBValue(AColor))); if Gray > 127 then Result := $000000 else Result := $FFFFFF; end; type TBitmapInfo1 = packed record bmiHeader: TBitmapInfoHeader; Color0: DWORD {TRGBQuad}; Color1: DWORD {TRGBQuad}; end; function CreateMonochromeBitmap(ADC: HDC; AWidth, AHeight: Integer): HBITMAP; const Alignment = 31; var BitmapInfo: TBitmapInfo1; Data: Pointer; begin ZeroMemory(@BitmapInfo, SizeOf(BitmapInfo)); with BitmapInfo, bmiHeader do begin biSize := SizeOf(bmiHeader); biWidth := AWidth; biHeight := -AHeight; biPlanes := 1; biBitCount := 1; biCompression := BI_RGB; biSizeImage := ((AWidth + Alignment) and not Alignment) div 8; biClrUsed := 2; biClrImportant := biClrUsed; Color0 := $000000; Color1 := $FFFFFF; end; Result := GDICheck(CreateDIBSection(ADC, PBitmapInfo(@BitmapInfo)^, DIB_RGB_COLORS, Data, 0, 0)); end; function CreateColorBitmap(AWidth, AHeight: Integer; AColor: COLORREF; ACheckBitmap: HBITMAP): HBITMAP; var Bitmap: Windows.TBitmap; BaseDC: HDC; Brush: HBRUSH; ACheckDC: HDC; ACheckOldDCBitmap: HBITMAP; CheckBitmap: HBITMAP; CheckDC: HDC; CheckOldDCBitmap: HBITMAP; ResultDC: HDC; ResultOldBitmap: HBITMAP; TempBitmap: HBITMAP; TempDC: HDC; TempOldDCBitmap: HBITMAP; Theme: HTHEME; begin BaseDC := GDICheck(GetDC(0)); try if ACheckBitmap <> 0 then GDICheck(GetObject(ACheckBitmap, SizeOf(Bitmap), @Bitmap)); if (ACheckBitmap <> 0) and (Bitmap.bmWidth < AWidth) or (Bitmap.bmHeight < AHeight) then begin CheckBitmap := GDICheck(CreateMonochromeBitmap(BaseDC, AWidth, AHeight)); try CheckDC := GDICheck(CreateCompatibleDC(BaseDC)); try CheckOldDCBitmap := SelectObject(CheckDC, CheckBitmap); try Brush := GDICheck(CreateSolidBrush($FFFFFF)); try GDICheck(FillRect(CheckDC, Rect(0, 0, AWidth, AHeight), Brush)); ACheckDC := GDICheck(CreateCompatibleDC(BaseDC)); try ACheckOldDCBitmap := SelectObject(ACheckDC, ACheckBitmap); try GDICheck(BitBlt(CheckDC, (AWidth - Bitmap.bmWidth) div 2 + 1, (AHeight - Bitmap.bmHeight) div 2, Bitmap.bmWidth, Bitmap.bmHeight, ACheckDC, 0, 0, SRCCOPY)); finally SelectObject(ACheckDC, ACheckOldDCBitmap); end; finally DeleteDC(ACheckDC); end; finally DeleteObject(Brush); end; finally SelectObject(CheckDC, CheckOldDCBitmap); end; finally DeleteDC(CheckDC); end; except DeleteObject(CheckBitmap); raise; end; end else CheckBitmap := ACheckBitmap; try Result := GDICheck(CreateCompatibleBitmap(BaseDC, AWidth, AHeight)); ResultDC := GDICheck(CreateCompatibleDC(BaseDC)); try ResultOldBitmap := SelectObject(ResultDC, Result); try Brush := GDICheck(CreateSolidBrush(AColor)); try GDICheck(FillRect(ResultDC, Rect(0, 0, AWidth, AHeight), Brush)); finally DeleteObject(Brush); end; if CheckBitmap <> 0 then {if IsWindowsVistaOrLater and ThemeServices.Available and ThemeServices.Enabled then begin Theme := OpenThemeData(0, VSCLASS_MENU); try DrawThemeBackground(Theme, ResultDC, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, Rect(0, 0, AWidth, AHeight), nil); finally CloseThemeData(Theme); end; end else} begin TempBitmap := GDICheck(CreateCompatibleBitmap(BaseDC, AWidth, AHeight)); try TempDC := GDICheck(CreateCompatibleDC(BaseDC)); try TempOldDCBitmap := SelectObject(TempDC, TempBitmap); try Brush := GDICheck(CreateSolidBrush(GetOptimalCheckColor(AColor))); try GDICheck(FillRect(TempDC, Rect(0, 0, AWidth, AHeight), Brush)); finally DeleteObject(Brush); end; GDICheck(MaskBlt(ResultDC, 0, 0, AWidth, AHeight, TempDC, 0, 0, CheckBitmap, 0, 0, MAKEROP4($00AA0029, SRCCOPY))); finally SelectObject(TempDC, TempOldDCBitmap); end; finally DeleteDC(TempDC); end; finally DeleteObject(TempBitmap); end; end; finally SelectObject(ResultDC, ResultOldBitmap); end; finally DeleteDC(ResultDC); end; finally if (CheckBitmap <> 0) and (CheckBitmap <> ACheckBitmap) then DeleteObject(CheckBitmap) end; finally ReleaseDC(0, BaseDC) end; end; procedure AddMarkerMenuItem(AMenu: HMENU; AColor: COLORREF; AID: UINT; AChecked: Boolean; AEnabled: Boolean = True); var MI: MENUITEMINFO; Bitmap: Windows.TBitmap; W, H: Integer; Theme: HTHEME; CheckBitmap: HBITMAP; CheckDC: HDC; CheckOldDCBitmap: HBITMAP; begin ZeroMemory(@MI, SizeOf(MI)); MI.cbSize := SizeOf(MI); MI.fMask := MIIM_FTYPE or MIIM_ID or MIIM_STATE or MIIM_STRING; MI.fType := MFT_STRING; MI.wID := AID; MI.dwTypeData := 'Marker'; MI.cch := Length(MI.dwTypeData); if AEnabled then MI.fState := MFS_ENABLED else MI.fState := MFS_DISABLED; if AChecked then MI.fState := MI.fState or MFS_CHECKED; CheckBitmap := GDICheck(LoadBitmap(0, PChar(OBM_CHECK))); try GDICheck(GetObject(CheckBitmap, SizeOf(Bitmap), @Bitmap)); if IsWindowsVistaOrLater then begin MI.fMask := MI.fMask or MIIM_BITMAP; W := GetSystemMetrics(SM_CXSMICON); H := GetSystemMetrics(SM_CYSMICON); if AChecked then begin CheckBitmap := GDICheck(LoadBitmap(0, PChar(OBM_CHECK))); try MI.hbmpItem := CreateColorBitmap(W, H, AColor, CheckBitmap); finally DeleteObject(CheckBitmap) end; end else MI.hbmpItem := CreateColorBitmap(W, H, AColor, 0); end else begin MI.fMask := MI.fMask or MIIM_CHECKMARKS; MI.hbmpChecked := CreateColorBitmap(Bitmap.bmWidth - 2, Bitmap.bmHeight, AColor, CheckBitmap); MI.hbmpUnchecked := CreateColorBitmap(Bitmap.bmWidth - 2, Bitmap.bmHeight, AColor, 0); end; finally DeleteObject(CheckBitmap) end; InsertMenuItem(AMenu, GetMenuItemCount(AMenu), True, MI); end; 
+2
source

While I do not use color swatches, and, strictly speaking, MFC, I am doing bitmap rendering in my derived menu items. You must be able to adapt the following to your needs.

When measuring element text, I use the dc desktop.

 CClientDC dc(CWnd::GetDesktopWindow()); SIZE size; GetTextExtentPoint32(dc.m_hDC, buff, buff.GetLength(), &size ); lpMeasureItemStruct->itemWidth = size.cx+12; lpMeasureItemStruct->itemHeight = size.cy+8; 

Along with some small adjustments through experiments, I came up with what I need for size.

To display the actual bitmap and text, I check if the theme is active and displays the menu item in one of two ways. Either as a thematic menu item, or a standard menu item. To make the text, I start with the rectangle that was passed through LPDRAWITEMSTRUCT. Then I do the following setup before rendering the text.

 // adjust if non-themed. if (!IsThemeActive()) rectt.left+= m_bmWidth+4; else rectt.left+= BITMAP_ADJUSTMENT; 

Through a trial version and an error, I found that BITMAP_ADJUSTMENT, equal to 30, works for me. Then, depending on the installed item (disabled, selected), the correction is corrected further.

 // draw disabled text. if (disabled) { // draw selected text. if (selected) { pDC->SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); pDC->DrawText(text, &rectt, format); } else { offset = rectt; offset.left+= 1; offset.right+=1; offset.top+= 1; offset.bottom+= 1; pDC->SetTextColor(::GetSysColor(COLOR_BTNHILIGHT)); pDC->DrawText(text, &offset, format); pDC->SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); pDC->DrawText(text, &rectt, format); } } else // draw normal text. pDC->DrawText(text, &rectt, format); 

Finally, to display the checkmark, I create a list of images using a predefined bitmap (maybe I extracted it from the Microsoft dll). Again, the bitmap is displayed according to the state of the item.

  // draw non-disabled bitmap. if (!disabled) { bmp.GetBitmap(&bm); m_bmWidth = bm.bmWidth; imgList.Create(bm.bmWidth, bm.bmWidth, ILC_COLOR24|ILC_MASK, 1, 1); imgList.Add(&bmp, COLOR_BITMAP_BACKGROUND); if (checked) { if (!selected) imgList.DrawEx(pDC, 0, CPoint(4,rect.top+4), CSize(bm.bmWidth, bm.bmWidth), COLOR_NOT_SELECTED, 0, ILD_NORMAL); else imgList.DrawEx(pDC, 0, CPoint(4,rect.top+4), CSize(bm.bmWidth, bm.bmWidth), 0, COLOR_SELECTED, ILD_SELECTED); } else imgList.DrawEx(pDC, 0, CPoint(4,rect.top+4), CSize(bm.bmWidth, bm.bmWidth), 0, 0, ILD_TRANSPARENT); } else // draw a disabled bitmap. AfxDrawGrayBitmap(pDC, 4, rect.top+4, bmp, ::GetSysColor(COLOR_3DFACE)); 

Most of the rendering was done using an iterative approach that tuned straight objects after each attempt.

+1
source

All Articles