如何编写自动缩放到系统字体和dpi设置的WinForms代码?

简介:有很多评论说“WinForms不能自动缩放到DPI /字体设置,切换到WPF”。 但是,我认为这是基于.NET 1.1; 看来他们在.NET 2.0中实现了自动扩展的function。 至less根据我们的研究和testing到目前为止。 但是,如果你们中有些人知道的更好,我们很乐意听到你的消息。 (请不要争论我们应该切换到WPF …这不是一个选项。)

问题:

  • 什么在WinForms不能自动缩放,因此应该避免?

  • 在编写WinForms代码时,程序员应遵循什么样的devise指导方针,使其能自动扩展?

我们已经确定的devise指南迄今为止:

请参阅下面的社区wiki答案 。

这些是不正确还是不足? 我们应该采用其他指导方针吗? 还有其他的模式需要避免吗? 对此的任何其他指导将非常感激。

不支持正确缩放的控件:

  • AutoSize = FalseFontinheritance的Label 。 在控件上显式设置Font ,使其在“属性”窗口中以粗体显示。
  • ListView列的宽度不能缩放。 重写窗体的ScaleControl来代替它。 看到这个答案
  • SplitContainerPanel1MinSizePanel2MinSizeSplitterDistance属性
  • 带有MultiLine = True TextBox MultiLine = TrueFontinheritance。 在控件上显式设置Font ,使其在“属性”窗口中以粗体显示。
  • ToolStripButton的图像。 在窗体的构造函数中:

    • 设置ToolStrip.AutoSize = False
    • 根据CreateGraphics.DpiX.DpiY设置ToolStrip.ImageScalingSize
    • 设置ToolStrip.AutoSize = True如果需要,则为ToolStrip.AutoSize = True

    有时AutoSize可以保留为True但有时无法在没有这些步骤的情况下resize。 在.NET Framework 4.5.2和EnableWindowsFormsHighDpiAutoResizing下没有任何更改。

  • TreeView的图像。 根据CreateGraphics.DpiX.DpiY设置ImageList.ImageSize 。 在.NET Framework 4.5.1和EnableWindowsFormsHighDpiAutoResizing下没有更改。
  • Form的大小。 在创build后手动缩放固定大小的Form

devise指南:

  • 所有ContainerControls必须设置为相同的AutoScaleMode = Font 。 (字体将同时处理DPI更改和系统字体大小设置的更改; DPI将只处理DPI更改,而不更改系统字体大小设置。)

  • 所有ContainerControls也必须使用AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); ,假设96dpi(见下一个项目符号)。 这是由devise师根据您打开devise师的DPI自动添加的,但是很多我们最古老的devise器文件都没有。 也许Visual Studio .NET(VS 2005之前的版本)没有正确添加。

  • 所有的devise师都能以96dpi的速度工作(我们可能可以切换到120dpi;但是互联网上的智慧说要坚持96dpi;实验是按顺序进行的;通过devise,不要紧,因为它只是改变AutoScaleDimensions行devise师插入)。 要将Visual Studio设置为在高分辨率显示器上以虚拟96dpi运行,请find其.exe文件,右键单击以编辑属性,然后在兼容性下select“覆盖高DPI缩放行为。缩放执行方式:系统”。

  • 确保你永远不要在容器级别设置字体…只在叶子控件上。 (在容器上设置字体似乎closures了该容器的自动缩放。)

  • 不要使用Anchor RightBottom锚定到UserControl …其定位不会自动缩放; 相反,放置一个面板或其他容器到你的用户控件,并锚定你的其他控件到该面板; 面板在您的UserControl中使用Dock Right或Dock Bottom

  • 只有控件中的控件列出了在调用InitializeComponent结束时的ResumeLayout时才会自动缩放…如果您dynamic添加控件,则需要SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout(); 在添加之前,在该控件上。如果您不使用Dock模式或者像FlowLayoutPanelTableLayoutPanel这样的布局pipe理器,则还需要调整您的位置。

  • ContainerControl派生的基类应将AutoScaleMode设置为Inherit (在类ContainerControl设置的默认值;但不是由devise者设置的默认值)。 如果将其设置为其他任何内容,然后派生类尝试将其设置为Font(应该如此),那么将其设置为Font将清除devise者的AutoScaleDimensions设置,从而实际上切换自动缩放! (这个指南和前面的一个结合,意味着你永远不能在devise器中实例化基类……所有的类都需要被devise为基类或叶类)

  • 避免在devise器中静态/使用Form.MaxSize 。 窗体上的MinSizeMaxSize不会像其他任何一样缩放。 因此,如果您以96dpi的速度完成所有工作,那么当DPI较高时,您的MinSize不会造成问题,但可能不如预期的那样严格,但MaxSize可能会限制Size的缩放比例,这可能会导致问题。 如果你想MinSize == Size == MaxSize ,不要在devise器中这样做……在你的构造函数或OnLoad覆盖中…将MinSizeMaxSize设置为正确缩放的Size。

  • 特定PanelContainer上的所有控件都应使用锚定或对接。 如果将它们混合在一起,该Panel所做的自动缩放常常会以微妙的怪异方式行事不端。

我的经验和现在的最高投票答案是完全不同的。 通过介绍.NET框架代码并仔细阅读参考源代码,我得出结论认为,所有东西都可以用于自动缩放工作,并且在某处将其搞乱。 事实certificate这是事实。

如果你创build了一个适当的可重排/自动大小的布局,那么几乎所有的东西都会自动地使用Visual Studio使用的默认设置(即,AutoSizeMode =父窗体上的Font,inheritance其他)。

唯一的问题是如果你在devise器中设置了表单的Font属性。 生成的代码将按字母顺序对分配进行sorting,这意味着AutoScaleDimensions Font 之前分配。 不幸的是,这完全打破了WinForms自动缩放逻辑。

虽然修复很简单。 要么根本不在devise器中设置Font属性(在表单构造器中设置它),要么手动重新sorting这些赋值(但是,每次在devise器中编辑表单时都必须保持这样做)。 瞧,几乎完美和全自动缩放与最小的麻烦。 即使表单大小正确缩放。


当我遇到他们时,我会列出已知的问题:

  • 嵌套的TableLayoutPanel 错误地计算控件边距 。 没有已知的解决方法,避免了边距和填充 – 或避免嵌套的表格布局面板。

我在工作中写的一个指南:

WPF工作在“独立于设备的单元”,这意味着所有的控件都可以完美地适应高分辨率的屏幕 在WinForms中,需要更多的关心。

WinForms的像素。 文本将根据系统dpi进行缩放,但通常会由未缩放的控件裁剪。 为了避免这样的问题,你必须避免明确的大小和定位。 遵守这些规则:

  1. 无论你在哪里find它(标签,button,面板),都将AutoSize属性设置为True。
  2. 对于布局,使用FlowLayoutPanel(一个WPF的StackPanel)和TableLayoutPanel(一个WPF的网格)布局,而不是香草面板。
  3. 如果您正在使用高dpi计算机进行开发,Visual Studiodevise人员可能会感到沮丧。 当您设置AutoSize = True时,它将调整控件到您的屏幕。 如果控件具有AutoSizeMode = GrowOnly,它将保持这个大小为正常的dpi的人,即。 大于预期。 要解决这个问题,请使用普通dpi在计算机上打开devise器,然后右键单击,重置。

我发现很难让WinForms在高DPI上performance出色。 所以,我写了一个VB.NET方法来覆盖表单行为:

 Public Shared Sub ScaleForm(WindowsForm As System.Windows.Forms.Form) Using g As System.Drawing.Graphics = WindowsForm.CreateGraphics Dim sngScaleFactor As Single = 1 Dim sngFontFactor As Single = 1 If g.DpiX > 96 Then sngScaleFactor = g.DpiX / 96 'sngFontFactor = 96 / g.DpiY End If If WindowsForm.AutoScaleDimensions = WindowsForm.CurrentAutoScaleDimensions Then 'ucWindowsFormHost.ScaleControl(WindowsForm, sngFontFactor) WindowsForm.Scale(sngScaleFactor) End If End Using End Sub 

将您的.Net Framework 4.7应用程序定位并在Windows 10 v1703(Creators Update Build 15063)下运行。 在Windows 10(v1703)下的.Net 4.7中,MS进行了大量的DPI改进 。

从.NET Framework 4.7开始,Windows Forms包含对常见的最高DPI和dynamicDPIscheme的增强。 这些包括:

  • 在一些Windows窗体控件(如MonthCalendar控件和CheckedListBox控件)的缩放和布局方面的改进。

  • 单通缩放。 在.NET Framework 4.6和更早版本中,缩放是通过多遍执行的,这导致一些控件的缩放比必要的更多。

  • 支持在Windows Forms应用程序启动后用户更改DPI或缩放因子的dynamicDPIscheme。

为了支持它,请在您的应用程序中添加一个应用程序清单,并指出您的应用程序支持Windows 10:

 <compatibility xmlns="urn:schemas-microsoft.comn:compatibility.v1"> <application> <!-- Windows 10 compatibility --> <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> </application> </compatibility> 

接下来,添加一个app.config并声明每个监视器意识的应用程序。 这是现在在app.config中完成,而不是像以前那样在清单中!

 <System.Windows.Forms.ApplicationConfigurationSection> <add key="DpiAwareness" value="PerMonitorV2" /> </System.Windows.Forms.ApplicationConfigurationSection> 

PerMonitorV2是Windows 10创build者更新以来的新增function:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

也称为Per Monitor v2。 原始每个监视器DPI感知模式的进步,使得应用程序能够在每个顶层窗口的基础上访问新的DPI相关的缩放行为。

  • 子窗口DPI更改通知 – 在每个监视器v2上下文中,将通知整个窗口树发生任何DPI更改。

  • 非客户区域的缩放 – 所有的窗口都会自动将其非客户区域以DPI敏感的方式绘制。 调用EnableNonClientDpiScaling是不必要的。

  • Win32菜单的调整 – 在Per Monitor v2上下文中创build的所有NTUSER菜单将按照监视器的方式进行缩放。

  • 对话缩放 – 在Per Monitor v2上下文中创build的Win32对话框将自动响应DPI更改。

  • 改进了comctl32控件的缩放 – 各种comctl32控件在Per Monitor v2上下文中改进了DPI缩放行为。

  • 改进主题行为 – 在每个监视器v2窗口的上下文中打开的UxTheme句柄将根据与该窗口关联的DPI进行操作。

现在您可以订阅3个新事件来获取有关DPI更改的通知:

  • Control.DpiChangedAfterParent被触发当控件的DPI设置在其父控件或窗体的DPI更改事件发生后以编程方式更改时发生。

  • Control.DpiChangedBeforeParent ,当一个控件的DPI设置被编程更改为其父控件或窗体的DPI更改事件发生时触发。

  • Form.DpiChanged ,当DPI设置在当前显示窗体的显示设备上发生变化时触发。

您还有3个关于DPI处理/缩放的辅助方法:

  • Control.LogicalToDeviceUnits ,将值从逻辑转换为设备像素。

  • Control.ScaleBitmapLogicalToDevice ,将位图图像缩放为设备的逻辑DPI。

  • Control.DeviceDpi ,它返回当前设备的DPI。

如果您仍然遇到问题,可以通过app.config条目select退出DPI改进 。

如果您无权访问源代码,则可以转到Windows资源pipe理器中的应用程序属性,转到兼容性并selectSystem (Enhanced)

在这里输入图像说明

激活GDI缩放以改善DPI处理:

对于基于GDI的应用程序,Windows现在可以按照每个监视器的DPI进行扩展。 这意味着这些应用程序将神奇地变成每个监视器DPI的意识。

执行所有这些步骤,并且您应该对WinForms应用程序获得更好的DPI体验。 但请记住,您需要针对.net 4.7的应用程序,至less需要Windows 10 Build 15063(创作者更新)。 在下一个Windows 10 Update 1709中,我们可能会得到更多的改进。

除了锚不能很好的工作:我会更进一步说,确切的定位(也就是使用位置属性)不适合字体缩放。 我不得不在两个不同的项目中解决这个问题。 在这两者中,我们必须将所有的WinForms控件的定位转换为使用TableLayoutPanel和FlowLayoutPanel。 使用TableLayoutPanel中的Dock(通常设置为Fill)属性工作得很好,并且可以很好地与系统字体DPI一起缩放。

我最近遇到了这个问题,尤其是在编辑器在高dpi系统上打开时,与Visual Studio重新缩放相结合。 我发现最好保持 AutoScaleMode = Font ,但是将Forms 字体设置为默认字体,但是指定像素大小 ,而不是点,即: Font = MS Sans; 11px Font = MS Sans; 11px 。 在代码中,我将字体重置为默认值: Font = SystemFonts.DefaultFont ,一切正常。

只是我的两分钱。 我以为我分享,因为“保持AutoScaleMode =字体”“为devise师设置像素字体大小”是我没有在互联网上find的东西。

我在我的博客上有更多的细节: http : //www.sgrottel.de/? p = 1581&lang= en