如何编写自动缩放到系统字体和dpi设置的WinForms代码?
简介:有很多评论说“WinForms不能自动缩放到DPI /字体设置,切换到WPF”。 但是,我认为这是基于.NET 1.1; 看来他们在.NET 2.0中实现了自动扩展的function。 至less根据我们的研究和testing到目前为止。 但是,如果你们中有些人知道的更好,我们很乐意听到你的消息。 (请不要争论我们应该切换到WPF …这不是一个选项。)
问题:
-
什么在WinForms不能自动缩放,因此应该避免?
-
在编写WinForms代码时,程序员应遵循什么样的devise指导方针,使其能自动扩展?
我们已经确定的devise指南迄今为止:
请参阅下面的社区wiki答案 。
这些是不正确还是不足? 我们应该采用其他指导方针吗? 还有其他的模式需要避免吗? 对此的任何其他指导将非常感激。
不支持正确缩放的控件:
-
AutoSize = False
和Font
inheritance的Label
。 在控件上显式设置Font
,使其在“属性”窗口中以粗体显示。 -
ListView
列的宽度不能缩放。 重写窗体的ScaleControl
来代替它。 看到这个答案 -
SplitContainer
的Panel1MinSize
,Panel2MinSize
和SplitterDistance
属性 - 带有
MultiLine = True
TextBox
MultiLine = True
和Font
inheritance。 在控件上显式设置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