You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自绘托盘子菜单时GetMenuString无法返回菜单项字符串的问题

问题

为应用程序托盘图标创建菜单时,给包含子菜单的菜单项设置OwnerDraw属性后,自绘菜单背景正常,但调用GetMenuString始终返回0,无法获取菜单项文本,导致所有项显示为硬编码的"Item"。

原绘制代码:

procedure DrawOpaMenuItem(Menu: HMENU; DC: HDC; itemID: Cardinal; itemRect: TRect; State: Cardinal);
var
  Text: PWideChar;
  TxtLen: Integer;
  FNT: HFONT;
  BkBrush: HBRUSH;
begin
  SetTextColor(DC, GetSysColor(COLOR_MENUTEXT));
  SetBkMode(DC, TRANSPARENT);
  FNT := GetMenuFont; //A function that uses SystemParametersInfo with SPI_GETNONCLIENTMETRICS to create a font based on lfMenuFont
  SelectObject(DC, FNT);
  BkBrush := CreateSolidBrush(RGB($40,$F0,$FF));

  if State and ODS_SELECTED = 1 then
    FillRect(DC, itemRect, GetSysColorBrush(COLOR_HIGHLIGHT))
  else
    FillRect(DC, itemRect, BkBrush);

  itemRect.Left := itemRect.Left + 10;

  TxtLen := GetMenuString(Menu, itemID, nil, 0, MF_BYCOMMAND); //returns only 0!!!

  if TxtLen <> 0 then
  begin
    GetMenuString(Menu, itemID, Text, TxtLen + 1, MF_BYCOMMAND);
    DrawTextEx(DC, Text, -1, itemRect, DT_SINGLELINE or DT_VCENTER, nil);
  end
  else
    DrawTextEx(DC, PWideChar('Item'), -1, itemRect, DT_SINGLELINE or DT_VCENTER, nil);

  DeleteObject(BkBrush);
  DeleteObject(FNT);
  SetBkMode(DC, BKMODE_LAST);
end;

WinProc调用代码:

function WinProc(hWnd: HWND; Msg: Cardinal; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  case Msg of
    {...}
    WM_MEASUREITEM:
    begin
      with PMEASUREITEMSTRUCT(lParam)^ do
      begin
        itemWidth := 100;
        itemHeight := GetSystemMetrics(SM_CYMENU);
      end;
    end;
    WM_DRAWITEM:
    begin
      with PDRAWITEMSTRUCT(lParam)^ do
      begin
        DrawOpaMenuItem(hwndItem, hDC, itemID, rcItem, itemState);
      end;
    end;
    {...}
  end;
  {...}
  Result := DefWindowProc(hWnd, Msg, wParam, lParam);
end;
解决方案

问题根源

  1. 子菜单项的itemID是位置索引而非命令ID:在WM_DRAWITEM中,子菜单的itemID字段实际代表菜单项在子菜单中的位置索引(从0开始),但调用GetMenuString时使用了MF_BYCOMMAND标志(按命令ID查找),导致找不到对应项,返回0。
  2. 未分配文本缓冲区:Text变量未分配内存,直接传递给GetMenuString会导致未定义行为。
  3. 错误的背景模式恢复:BKMODE_LAST不是合法的SetBkMode参数,无需手动恢复DC的背景模式,系统会在绘制完成后处理。

修正后的代码

procedure DrawOpaMenuItem(Menu: HMENU; DC: HDC; ItemPos: Cardinal; itemRect: TRect; State: Cardinal);
var
  Text: array[0..255] of WideChar; // 预分配足够的缓冲区
  TxtLen: Integer;
  FNT: HFONT;
  OldFont: HFONT;
  BkBrush: HBRUSH;
begin
  SetTextColor(DC, GetSysColor(COLOR_MENUTEXT));
  SetBkMode(DC, TRANSPARENT);
  
  FNT := GetMenuFont;
  OldFont := SelectObject(DC, FNT); // 保存旧字体,避免资源泄漏
  
  BkBrush := CreateSolidBrush(RGB($40,$F0,$FF));
  if (State and ODS_SELECTED) <> 0 then
    FillRect(DC, itemRect, GetSysColorBrush(COLOR_HIGHLIGHT))
  else
    FillRect(DC, itemRect, BkBrush);
  DeleteObject(BkBrush); // 及时释放画笔
  
  itemRect.Left := itemRect.Left + 10;

  // 使用MF_BYPOSITION按位置索引获取菜单文本
  TxtLen := GetMenuString(Menu, ItemPos, @Text[0], Length(Text), MF_BYPOSITION or MF_UNICODE);
  
  if TxtLen > 0 then
    DrawTextEx(DC, @Text[0], TxtLen, itemRect, DT_SINGLELINE or DT_VCENTER, nil)
  else
    DrawTextEx(DC, 'Item', -1, itemRect, DT_SINGLELINE or DT_VCENTER, nil);

  SelectObject(DC, OldFont); // 恢复旧字体
  DeleteObject(FNT); // 释放自定义字体
end;

WinProc调用调整

在WM_DRAWITEM中,直接传递itemID作为位置索引即可(子菜单的itemID就是位置):

WM_DRAWITEM:
begin
  with PDRAWITEMSTRUCT(lParam)^ do
  begin
    DrawOpaMenuItem(hwndItem, hDC, itemID, rcItem, itemState);
  end;
end;

额外说明

  • 顶层菜单项的itemID是命令ID,此时需要用MF_BYCOMMAND,但子菜单必须用MF_BYPOSITION。
  • 预分配缓冲区时,可根据实际需求调整大小,或者先调用GetMenuString获取长度后动态分配内存。
  • 操作GDI对象(如字体)时,必须保存旧对象并恢复,避免资源泄漏。

内容的提问来源于stack exchange,提问作者yonni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 21:13:25