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

自定义ToolStrip渲染器中如何测量ToolStripItem尺寸适配GDI+绘制

问题描述

基于.NET Framework开发WinForms应用时,为统一视觉风格,给ContextMenuStrip组件编写了自定义渲染器,使用GDI+的Graphics.DrawString()方法绘制菜单项文本,自定义渲染器核心代码如下:

private class CustomContextMenuRenderer : ToolStripProfessionalRenderer
{
    private static StringFormat fStringFormatLeft = new StringFormat()
    { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Center};
    private static StringFormat fStringFormatRight = new StringFormat()
    { Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Center };
        
    protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
    {
        StringFormat sf = (e.TextFormat & TextFormatFlags.Right) == 0 ? fStringFormatLeft : fStringFormatRight;
        using (SolidBrush brush = new SolidBrush(e.Item.ForeColor))
        {
            e.Graphics.DrawString(e.Text, e.TextFont, brush, e.TextRectangle, sf);
        }
    }
}

使用部分字体时会出现菜单项高度不足、文本显示不全的问题,原因是默认渲染器使用GDI的TextRenderer类计算文本尺寸,和GDI+DrawString的实际渲染尺寸存在偏差。需要找到适配GDI+渲染的菜单项尺寸测量、上报方案。


解决方案

问题根源是默认ToolStripProfessionalRenderer的尺寸计算逻辑和自定义GDI+绘制逻辑不匹配,只需要在自定义渲染器中重写OnMeasureItem方法,用GDI+的文本测量逻辑替换默认的GDI测量逻辑,保证测量规则和绘制规则完全一致即可。

实现要点

  • 测量文本时使用和绘制时完全相同的StringFormat配置,避免格式参数差异导致测量结果不准
  • 计算尺寸时要把图标区域、快捷键文本、内外边距的占用空间纳入计算,避免内容重叠
  • 仅当GDI+计算出的所需尺寸大于默认尺寸时覆盖原有值,保留系统默认的最小尺寸规则,避免菜单项过小

修正后的完整渲染器代码

private class CustomContextMenuRenderer : ToolStripProfessionalRenderer
{
    private static StringFormat fStringFormatLeft = new StringFormat()
    { 
        Alignment = StringAlignment.Near, 
        LineAlignment = StringAlignment.Center,
        FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.MeasureTrailingSpaces
    };
    private static StringFormat fStringFormatRight = new StringFormat()
    { 
        Alignment = StringAlignment.Far, 
        LineAlignment = StringAlignment.Center,
        FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.MeasureTrailingSpaces
    };
    // 布局常量,和系统默认ContextMenuStrip参数保持一致
    private const int ItemVerticalPadding = 4;
    private const int ItemHorizontalPadding = 4;
    private const int DefaultIconAreaWidth = 24;

    protected override void OnMeasureItem(ToolStripItemMeasureEventArgs e)
    {
        base.OnMeasureItem(e);
        // 仅处理下拉菜单中的普通菜单项,跳过分隔符、顶级菜单项
        if (e.Item is not ToolStripMenuItem menuItem || !menuItem.IsOnDropDown)
            return;

        // 选择和绘制逻辑一致的文本格式
        StringFormat sf = (e.TextFormat & TextFormatFlags.Right) == 0 ? fStringFormatLeft : fStringFormatRight;
        // 用GDI+接口测量主文本尺寸
        SizeF mainTextSize = e.Graphics.MeasureString(menuItem.Text, menuItem.Font, int.MaxValue, sf);
        
        // 测量快捷键文本尺寸
        SizeF shortcutTextSize = SizeF.Empty;
        if (!string.IsNullOrEmpty(menuItem.ShortcutKeyDisplayString))
        {
            shortcutTextSize = e.Graphics.MeasureString(menuItem.ShortcutKeyDisplayString, menuItem.Font, int.MaxValue, fStringFormatRight);
        }

        // 计算所需总高度:文本高度 + 上下边距
        int requiredHeight = (int)Math.Ceiling(mainTextSize.Height) + ItemVerticalPadding * 2;
        // 计算所需总宽度:图标区 + 主文本 + 快捷键区间距 + 快捷键文本 + 左右边距
        int requiredWidth = DefaultIconAreaWidth + (int)Math.Ceiling(mainTextSize.Width) + ItemHorizontalPadding * 2;
        if (!shortcutTextSize.IsEmpty)
        {
            requiredWidth += (int)Math.Ceiling(shortcutTextSize.Width) + ItemHorizontalPadding * 3;
        }

        // 覆盖默认尺寸,保证文本完整显示
        if (requiredHeight > e.ItemHeight)
            e.ItemHeight = requiredHeight;
        if (requiredWidth > e.ItemWidth)
            e.ItemWidth = requiredWidth;
    }

    protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
    {
        if (e.Item is not ToolStripMenuItem)
        {
            base.OnRenderItemText(e);
            return;
        }
        StringFormat sf = (e.TextFormat & TextFormatFlags.Right) == 0 ? fStringFormatLeft : fStringFormatRight;
        // 调整文本绘制区域高度和菜单项高度一致,保证垂直居中效果正确
        Rectangle drawRect = new Rectangle(
            e.TextRectangle.X,
            e.TextRectangle.Y,
            e.TextRectangle.Width,
            e.Item.Height
        );
        using (SolidBrush textBrush = new SolidBrush(e.Item.ForeColor))
        {
            e.Graphics.DrawString(e.Text, e.TextFont, textBrush, drawRect, sf);
        }
    }
}

适配说明

  • 如果修改了菜单项的图标大小、边距、快捷键显示规则,同步调整代码中对应的布局常量即可
  • 高DPI场景下,可以根据当前设备DPI缩放比例动态计算布局常量值,避免缩放后布局错位
  • 选中、禁用、悬停等不同状态的菜单项只要未单独设置特殊字体,不需要额外调整尺寸逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 09:36:25