WinForms ContextMenuStrip 中的复选标记区域在高 dpi 上很窄

问题描述

我们有一个适用于 .NET Framework 4.7.2 的 WinForms 应用程序。主窗体包含一个基于 ToolStrip 组件的工具栏。其中一个按钮显示基于 ContextMenuStrip 组件的下拉菜单。整个结构看起来像这样:

private ToolStrip MainToolStrip;
private toolstripdropdownButton TextSizeButton;
private ContextMenuStrip TextSizeMenu;

MainToolStrip = new ToolStrip();
TextSizeButton = new toolstripdropdownButton();
TextSizeMenu = new ContextMenuStrip();

MainToolStrip.Items.AddRange(new ToolStripItem[] {...,TextSizeButton,...});

TextSizeButton.DropDown = TextSizeMenu;

使用应用配置文件中的标准 <System.Windows.Forms.ApplicationConfigurationSection> 元素在应用中启用高 dpi 支持(根据 this Microsoft guide)。

这个下拉菜单在普通 96dpi 屏幕上看起来不错,但在高分辨率屏幕上图片不太好,因为复选标记的灰色区域宽度不够:

enter image description here

如何解决这个问题?

解决方法

很难具体设置图像/检查页边距的宽度。当我查看 .NET 源代码时,似乎有两种选择。选项 1) ToolStripMenuItem 对象之一必须有一个虚拟图像(例如高度 = 1 像素,宽度 = 所需的边距宽度),以便图像会强制边距为所需的宽度。这种方法的缺点是,如果 Font 大小发生变化,则需要生成新的虚拟图像。选项 2) 使用反射来访问某些 private 字段,并不理想,但这是两个选项中更容易的一个。这是选项 #2 的一些代码:

//</summary>Keeps the checkboxes square dimensions. The image margin is at least the height,or the width of the widest ToolStripItem image.</summary>
private class ContextMenuStrip2 : ContextMenuStrip {
    private static FieldInfo fieldScaledDefaultImageMarginWidth = null;
    private static FieldInfo fieldScaledDefaultImageSize = null;
    private static bool scaledFields = false;
    static ContextMenuStrip2() {
        try {
            Type ty = typeof(ToolStripDropDownMenu);
            fieldScaledDefaultImageMarginWidth = ty.GetField("scaledDefaultImageMarginWidth",BindingFlags.Instance | BindingFlags.NonPublic);
            fieldScaledDefaultImageSize = ty.GetField("scaledDefaultImageSize",BindingFlags.Instance | BindingFlags.NonPublic);
            scaledFields = (fieldScaledDefaultImageMarginWidth != null && fieldScaledDefaultImageSize != null);
        } catch {}
    }


    private int currentImageMarginWidth = -1;
    private bool isAdjusting = false;

    public ContextMenuStrip2() : base() {
    }

    protected override void OnLayout(LayoutEventArgs e) {
        base.OnLayout(e);

        if (!isAdjusting && scaledFields && this.Items.Count > 0) {
            int wMax = 0;
            foreach (ToolStripItem i in Items) {
                int h = i.Height + 3;
                if (h > wMax)
                    wMax = h;
                if (i.Image != null) {
                    int w = i.Image.Width + 4;
                    if (w > wMax)
                        wMax = w;
                }
            }

            if (wMax != currentImageMarginWidth) {
                currentImageMarginWidth = wMax;
                fieldScaledDefaultImageMarginWidth.SetValue(this,wMax);
                fieldScaledDefaultImageSize.SetValue(this,new Size(1000,0)); // must do this to cancel out extraImageWidth in CalculateInternalLayoutMetrics()
                isAdjusting = true;
                ResumeLayout(true);
                isAdjusting = false;
            }
        }
    }
}
,

为了避免与 ContextMenuStrip 中复选标记的标准实现相关的任何进一步的不兼容性和副作用,我们决定简单地使用我们自己的图像作为复选标记:

enter image description here enter image description here

它看起来比默认的更酷,因为默认实现会在复选标记周围绘制选择矩形(为什么?!):

enter image description here

切换检查状态的核心代码如下:

private Bitmap ImageCheck = CreateToolButtonResBitmap("check.png");
private Bitmap ImageEmpty = CreateToolButtonResBitmap("10tec-empty.png");

private void SetMenuItemChecked(ToolStripMenuItem item,bool check)
{
    if (check)
        item.Image = ImageCheck;
    else
        item.Image = ImageEmpty;
}

为了更好的效果,我们还移除了 ContextMenuStrip 左边不需要的灰色区域:

internal class DropDownToolbarRenderer : ToolStripProfessionalRenderer
{
    protected override void OnRenderImageMargin(ToolStripRenderEventArgs e)
    {
    }
}

private static DropDownToolbarRenderer fDropDownToolbarRenderer = new DropDownToolbarRenderer();

TextSizeMenu.Renderer = fDropDownToolbarRenderer;

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...