WinForms ContextMenuStrip 中的复选标记区域在高 dpi 时很窄
Area for check mark in WinForms ContextMenuStrip is narrow on high dpi
我们有一个适用于 .NET Framework 4.7.2 的 WinForms 应用程序。主窗体包含一个基于 ToolStrip component. One of the buttons displays a drop-down menu based on the 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>
元素(根据 this Microsoft guide)在应用中启用高 dpi 支持。
这个drop-down菜单在96dpi的普通屏幕上看起来还不错,但是在high-res屏幕上画面就不太好了,因为勾选标记的灰色区域宽度不够:
如何解决这个问题?
很难具体设置 image/check 边距的宽度。当我查看 .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
中复选标记的标准实施相关的任何进一步的不兼容性和副作用,我们决定简单地使用我们自己的图像作为复选标记:
它看起来比默认的更酷,因为默认实现在复选标记周围绘制选择矩形(为什么?!):
切换检查状态的核心部分代码如下:
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;
我们有一个适用于 .NET Framework 4.7.2 的 WinForms 应用程序。主窗体包含一个基于 ToolStrip component. One of the buttons displays a drop-down menu based on the 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>
元素(根据 this Microsoft guide)在应用中启用高 dpi 支持。
这个drop-down菜单在96dpi的普通屏幕上看起来还不错,但是在high-res屏幕上画面就不太好了,因为勾选标记的灰色区域宽度不够:
如何解决这个问题?
很难具体设置 image/check 边距的宽度。当我查看 .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
中复选标记的标准实施相关的任何进一步的不兼容性和副作用,我们决定简单地使用我们自己的图像作为复选标记:
它看起来比默认的更酷,因为默认实现在复选标记周围绘制选择矩形(为什么?!):
切换检查状态的核心部分代码如下:
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;