如何使用所有者绘制菜单获得与标准相同的外观?

时间:2018-03-21 10:41:31

标签: winapi menu ownerdrawn

我考虑在Windows应用程序中使用所有者绘图菜单,该菜单应与标准菜单具有相同的外观。 (原因:标准菜单在某些混合DPI情况下无法正常工作。)

目前我在 WM_MEASUREITEM 期间提供正确宽度时遇到问题。

这是记事本编辑菜单的屏幕截图,其中每个项目都有一个快捷方式。

standard menu

我们可以看到项目文本和快捷方式文本之间存在一个持续的差距,就好像它们是列一样。看起来好像项目文本的宽度和快捷方式文本的宽度是分开检索的,因为最长的项目文本“时间/日期”保留了适用于 Ctrl + A的快捷方式宽度虽然它只需要一个 F5

我找不到任何API功能,我可以单独给出项目文本的宽度和快捷方式文本,也没有找到任何指定差距大小的指标。

所以我的问题是:是否有可能在通常的 WM_MEASUREITEM 消息中实现所需的行为,如果是,如何?如果没有,是否有任何其他方法可以做到这一点,或者根本不可能?

2 个答案:

答案 0 :(得分:0)

这是how ReactOS does it

测量菜单项:

if ((p = wcschr( lpitem->Xlpstr, '\t' )) != NULL) {
    RECT tmprc = rc;
    LONG tmpheight;
    int n = (int)( p - lpitem->Xlpstr);
    /* Item contains a tab (only meaningful in popup menus) */
    /* get text size before the tab */
    txtheight = DrawTextW( hdc, lpitem->Xlpstr, n, &rc,
        DT_SINGLELINE|DT_CALCRECT);
    txtwidth = rc.right - rc.left;
    p += 1; /* advance past the Tab */
    /* get text size after the tab */
    tmpheight = DrawTextW( hdc, p, -1, &tmprc,
                           DT_SINGLELINE|DT_CALCRECT);
    lpitem->dxTab += txtwidth;
    txtheight = max( txtheight, tmpheight);
    txtwidth += MenuCharSize.cx + /* space for the tab */
                tmprc.right - tmprc.left; /* space for the short cut */
}

然后绘制它:

Text = lpitem->Xlpstr;
if(Text)
{
    for (i = 0; Text[i]; i++)
        if (Text[i] == L'\t' || Text[i] == L'\b')
            break;
}

if(lpitem->fState & MF_GRAYED)

DrawTextW( hdc, Text, i, &rect, uFormat);

/* paint the shortcut text */
if (!menuBar && L'\0' != Text[i])  /* There's a tab or flush-right char */
{
    if (L'\t' == Text[i])
    {
        rect.left = lpitem->dxTab;
        uFormat = DT_LEFT | DT_VCENTER | DT_SINGLELINE;
    }
    else
    {
        rect.right = lpitem->dxTab;
        uFormat = DT_RIGHT | DT_VCENTER | DT_SINGLELINE;
    }

    DrawTextW( hdc, Text + i + 1, -1, &rect, uFormat );
}

因此,要在菜单项中插入键盘加速器,只需使用制表符将其与项目文本分开即可。然后,测量和绘图代码会查找此制表符并相应地执行操作。

但请注意,键盘加速器在菜单中右对齐,就像在屏幕截图中一样(使用DrawText DT_RIGHT实现),绘图代码需要它使用'\b'字符与项目文本分隔,而不是制表符,除非我遗漏了某些内容,否则测量代码中不会考虑这些内容,因此您可能需要对此进行补偿。

答案 1 :(得分:0)

使用所有者绘制复制标准菜单是一个痛苦的世界。您必须打开和关闭视觉样式,助记符/ Access keys,辅助功能和所有未记录的指标。如果可以的话,最好只使用普通菜单。

每个Windows 10版本中的每个监视器DPI支持似乎都会发生变化。 1607添加了EnableNonClientDpiScaling,它扩展了菜单和其他非客户区域。 1703添加了Per Monitor v2,MSDN说这是关于PMv2:

  
      
  • 非客户区域的缩放 - 所有窗口将自动以DPI敏感方式绘制非客户区域。不需要调用EnableNonClientDpiScaling。
  •   
  • 扩展Win32菜单 - 在Per Monitor v2上下文中创建的所有NTUSER菜单都将按监视器方式进行扩展。
  •   

记事本是PMv2,它的菜单似乎工作正常:

Per-monitor menu example

Windows 8.1和< 10周年更新需要更多的工作,我建议您不要在这些系统上声明自己DPI,并让Windows为您缩放窗口(有些模糊),如果系统有多个显示器。