如何编写自动缩放到系统字体和dpi设置的WinForms代码?
简介:有很多评论说“WinForms不能自动缩放到DPI /字体设置,切换到WPF”。 但是,我认为这是基于.NET 1.1; 看来他们在.NET 2.0中实现了自动扩展的function。 至less根据我们的研究和testing到目前为止。 但是,如果你们中有些人知道的更好,我们很乐意听到你的消息。 (请不要争论我们应该切换到WPF …这不是一个选项。)
问题:
- 
什么在WinForms不能自动缩放,因此应该避免? 
- 
在编写WinForms代码时,程序员应遵循什么样的devise指导方针,使其能自动扩展? 
我们已经确定的devise指南迄今为止:
请参阅下面的社区wiki答案 。
这些是不正确还是不足? 我们应该采用其他指导方针吗? 还有其他的模式需要避免吗? 对此的任何其他指导将非常感激。
不支持正确缩放的控件:
-  AutoSize = False和Fontinheritance的Label。 在控件上显式设置Font,使其在“属性”窗口中以粗体显示。
-   ListView列的宽度不能缩放。 重写窗体的ScaleControl来代替它。 看到这个答案
-   SplitContainer的Panel1MinSize,Panel2MinSize和SplitterDistance属性
-  带有MultiLine = TrueTextBoxMultiLine = True和Fontinheritance。 在控件上显式设置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 Right或Bottom锚定到UserControl …其定位不会自动缩放; 相反,放置一个面板或其他容器到你的用户控件,并锚定你的其他控件到该面板; 面板在您的UserControl中使用DockRight或DockBottom。
- 
只有控件中的控件列出了在调用 InitializeComponent结束时的ResumeLayout时才会自动缩放…如果您dynamic添加控件,则需要SuspendLayout();AutoScaleDimensions = new SizeF(6F, 13F);AutoScaleMode = AutoScaleMode.Font;ResumeLayout();在添加之前,在该控件上。如果您不使用Dock模式或者像FlowLayoutPanel或TableLayoutPanel这样的布局pipe理器,则还需要调整您的位置。
- 
从 ContainerControl派生的基类应将AutoScaleMode设置为Inherit(在类ContainerControl设置的默认值;但不是由devise者设置的默认值)。 如果将其设置为其他任何内容,然后派生类尝试将其设置为Font(应该如此),那么将其设置为Font将清除devise者的AutoScaleDimensions设置,从而实际上切换自动缩放! (这个指南和前面的一个结合,意味着你永远不能在devise器中实例化基类……所有的类都需要被devise为基类或叶类)
- 
避免在devise器中静态/使用 Form.MaxSize。 窗体上的MinSize和MaxSize不会像其他任何一样缩放。 因此,如果您以96dpi的速度完成所有工作,那么当DPI较高时,您的MinSize不会造成问题,但可能不如预期的那样严格,但MaxSize可能会限制Size的缩放比例,这可能会导致问题。 如果你想MinSize == Size == MaxSize,不要在devise器中这样做……在你的构造函数或OnLoad覆盖中…将MinSize和MaxSize设置为正确缩放的Size。
- 
特定 Panel或Container上的所有控件都应使用锚定或对接。 如果将它们混合在一起,该Panel所做的自动缩放常常会以微妙的怪异方式行事不端。
我的经验和现在的最高投票答案是完全不同的。 通过介绍.NET框架代码并仔细阅读参考源代码,我得出结论认为,所有东西都可以用于自动缩放工作,并且在某处将其搞乱。 事实certificate这是事实。
如果你创build了一个适当的可重排/自动大小的布局,那么几乎所有的东西都会自动地使用Visual Studio使用的默认设置(即,AutoSizeMode =父窗体上的Font,inheritance其他)。
 唯一的问题是如果你在devise器中设置了表单的Font属性。 生成的代码将按字母顺序对分配进行sorting,这意味着AutoScaleDimensions将在 Font 之前分配。 不幸的是,这完全打破了WinForms自动缩放逻辑。 
 虽然修复很简单。 要么根本不在devise器中设置Font属性(在表单构造器中设置它),要么手动重新sorting这些赋值(但是,每次在devise器中编辑表单时都必须保持这样做)。 瞧,几乎完美和全自动缩放与最小的麻烦。 即使表单大小正确缩放。 
当我遇到他们时,我会列出已知的问题:
-  嵌套的TableLayoutPanel错误地计算控件边距 。 没有已知的解决方法,避免了边距和填充 – 或避免嵌套的表格布局面板。
我在工作中写的一个指南:
WPF工作在“独立于设备的单元”,这意味着所有的控件都可以完美地适应高分辨率的屏幕 在WinForms中,需要更多的关心。
WinForms的像素。 文本将根据系统dpi进行缩放,但通常会由未缩放的控件裁剪。 为了避免这样的问题,你必须避免明确的大小和定位。 遵守这些规则:
- 无论你在哪里find它(标签,button,面板),都将AutoSize属性设置为True。
- 对于布局,使用FlowLayoutPanel(一个WPF的StackPanel)和TableLayoutPanel(一个WPF的网格)布局,而不是香草面板。
- 如果您正在使用高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