我只是想在我的上下文菜单中添加小色样(通过TrackPopupMenu API显示。)这是我想要实现的Photoshop版本:
据我所知,默认菜单不支持它。顺便说一句,上面的样本(没有颜色样本)是通过这样做生成的:
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));
所以我发现我需要使用MFT_OWNERDRAW
样式自己绘制菜单项,但这就是问题的开始。
我更改了代码以显示我的菜单:
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));
然后我需要覆盖WM_MEASUREITEM
和WM_DRAWITEM
条消息。但是,当我使用下面将要显示的代码执行此操作时,这就是我得到的内容:
所以请耐心等待。关于这个话题我有几个问题:
1)在处理WM_MEASUREITEM
时,如果菜单中既没有提供DC
也不提供HWND
,我应该如何知道文字的大小?换句话说,如果我这样做,菜单的大小是错误的:
#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)然后在处理WM_DRAWITEM
时如何知道开始在左侧绘制文本的偏移量?如果我这样做,我的菜单就没有向右偏移(正如你从上面的截图中看到的那样):
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)最后如何在菜单项左侧绘制默认复选框?
答案 0 :(得分:2)
使用颜色图标的替代方式。
Windows Vista +:
Windows XP:
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;
答案 1 :(得分:1)
虽然我不使用颜色样本,并且严格使用MFC,但我会在派生的菜单项上渲染位图。您应该能够根据自己的需要调整以下内容。
在测量项目文本时,我使用桌面直流。
CClientDC dc(CWnd::GetDesktopWindow());
SIZE size;
GetTextExtentPoint32(dc.m_hDC, buff, buff.GetLength(), &size );
lpMeasureItemStruct->itemWidth = size.cx+12;
lpMeasureItemStruct->itemHeight = size.cy+8;
除了通过实验进行的一些微小调整之外,我想出了我需要的尺寸。
为了渲染实际的位图和文本,我检查主题是否处于活动状态,并以两种不同的方式呈现菜单项。作为主题菜单项或标准菜单项。为了渲染文本,我从通过LPDRAWITEMSTRUCT传递的rect开始。然后,我在渲染文本之前进行以下调整。
// adjust if non-themed.
if (!IsThemeActive())
rectt.left+= m_bmWidth+4;
else
rectt.left+= BITMAP_ADJUSTMENT;
通过反复试验,我发现BITMAP_ADJUSTMENT设置为30对我有效。然后,根据项目的说明(禁用,选择),进一步调整 rectt 。
// 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);
最后,为了呈现复选标记,我使用预定义的位图创建了一个图像列表(我可能已经从Microsoft dll中提取了它)。同样,根据项目的状态呈现位图。
// 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));
大多数渲染是通过迭代方法在每次尝试后调整rect对象来完成的。