【问题标题】:Area for check mark in WinForms ContextMenuStrip is narrow on high dpiWinForms ContextMenuStrip 中复选标记的区域在高 dpi 上很窄
【发布时间】:2021-08-19 21:11:09
【问题描述】:

我们有一个适用于 .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 屏幕上看起来不错,但在高分辨率屏幕上图片就不那么好了,因为复选标记的灰色区域宽度不够:

如何解决这个问题?

【问题讨论】:

    标签: .net winforms drop-down-menu contextmenustrip highdpi


    【解决方案1】:

    很难具体设置图像/检查边距的宽度。当我查看.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;
                }
            }
        }
    }
    

    【讨论】:

    • 您可以覆盖 ToolStripProfessionalRenderer.OnRenderImageMargin() 和 OnRenderItemImage() 或 OnRenderItemCheck() -- 或者让应用在 Dpi 而不是字体上缩放。或两者兼而有之。
    • @jimi 这不会改变图像边距的宽度。
    • 您可以在需要的位置渲染图像或检查符号。菜单项也可以移动,如图here。您可以随意渲染整个菜单。
    • app.config 中还有Toolstrip.DisableHighDpiImprovements 设置。目前尚不清楚 OP 选择或修改了哪些选项。
    • 当你覆盖OnRenderImageMargin()时,你可以创建一个新的ToolStripRenderEventArgs对象(它不是密封的),膨胀e.AffectedBounds矩形,将新的矩形传递给ToolStripRenderEventArgs构造函数并调用@ 987654335@ 与新对象(最后评论,我保证 :)。
    【解决方案2】:

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

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

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

    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;
    

    【讨论】:

      猜你喜欢
      • 2019-02-15
      • 2010-12-24
      • 1970-01-01
      • 2018-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多