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


143

简介:有很多评论说“ WinForms不能很好地自动缩放到DPI /字体设置;切换到WPF”。但是,我认为它基于.NET 1.1。看来他们实际上在.NET 2.0中实现自动缩放方面做得很好。至少根据我们到目前为止的研究和测试。但是,如果你们当中的一些人知道得更多,我们很乐意听到您的消息。(请不要打扰我们应该切换到WPF ...这不是当前的选择。)

问题:

  • WinForms中的哪些内容不能正确自动缩放,因此应避免?

  • 程序员在编写WinForms代码时应遵循哪些设计准则,以使其能够很好地自动缩放?

到目前为止,我们已经确定的设计准则:

请参阅下面的社区Wiki答案

那些不正确或不足吗?我们还应该采用其他准则吗?还有其他需要避免的模式吗?任何其他对此的指导将不胜感激。

Answers:


127

不支持正确缩放的控件:

  • LabelAutoSize = FalseFont继承。Font在控件上明确设置,使其在“属性”窗口中以粗体显示。
  • ListView列宽不缩放。改写表单ScaleControl即可。看到这个答案
  • SplitContainerPanel1MinSizePanel2MinSizeSplitterDistance属性
  • TextBoxMultiLine = TrueFont继承。Font在控件上明确设置,使其在“属性”窗口中以粗体显示。
  • ToolStripButton的图像。在表单的构造函数中:

    • ToolStrip.AutoSize = False
    • ToolStrip.ImageScalingSize根据CreateGraphics.DpiX和设置.DpiY
    • ToolStrip.AutoSize = True根据需要设置。

    有时AutoSize可以保留下来,True但有时如果没有这些步骤就无法调整大小。使用.NET Framework 4.5.2.NET无需更改即可工作EnableWindowsFormsHighDpiAutoResizing

  • TreeView的图片。ImageList.ImageSize根据CreateGraphics.DpiX和进行设置.DpiY。对于.NET Framework 4.5.1.NETStateImageList,无需进行任何更改即可工作。EnableWindowsFormsHighDpiAutoResizing
  • Form的大小。Form创建后手动缩放固定大小。

设计准则:

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

  • 还必须将所有ContainerControl设置为相同AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);,假定为96dpi(请参阅下一个项目符号)和MS Sans Serif的默认字体(请参阅第二个项目符号)。设计器会根据您在...中打开设计器的DPI对其进行自动添加,但是在我们许多最早的设计器文件中都没有此功能。也许Visual Studio .NET(VS 2005之前的版本)没有正确添加它。

  • 不要在96DPI所有的设计工作(我们也许能够切换至120DPI;但在互联网上的智慧说,坚持96DPI,实验是为了那里;在设计上,这一问题并不应该因为它只是改变了AutoScaleDimensions行设计者插入)。要将Visual Studio设置为在高分辨率显示器上以虚拟96dpi运行,请找到其.exe文件,右键单击以编辑属性,然后在“兼容性”下选择“替代高DPI缩放行为。缩放比例:系统”。

  • 如果您想要除MS Sans Serif之外的应用程序范围的默认字体,请确保不要仅在容器级别上设置Font,而仅在叶控件或最基本Form的构造函数中设置。(在容器上设置字体似乎会关闭该容器的自动缩放,因为按字母顺序排在AutoScaleMode和AutoScaleDimensions设置之后。)请注意,如果确实在最基本的Form的构造函数中更改了Font,则会导致您的AutoScaleDimensions的计算方式与6x13不同;特别是,如果更改为Segoe UI(Win 10默认字体),它将是7x15 ...您将需要触摸Designer中的每个Form,以便它可以重新计算该.designer文件中的所有尺寸,包括的AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);

  • 不要使用锚点RightBottom锚定到UserControl上……它的位置不会自动缩放;而是将面板或其他容器放入您的UserControl中,然后将其他控件锚定到该面板上;该面板使用码头RightBottomFill在你的用户控件。

  • 只有在“控件”列表ResumeLayout中位于末尾的控件InitializeComponent才会被自动缩放...如果您动态添加控件,则需要先添加该控件,然后再SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();调整其位置。如果您未使用Dock模式或Layout Manager(例如FlowLayoutPanel或)TableLayoutPanel

  • 从其派生的基类ContainerControl应保留AutoScaleModeInherit(类中的默认值ContainerControl;但不是设计人员的默认值)。如果将其设置为其他任何值,然后派生类尝试将其设置为Font(应如此),则将其设置为的行为Font将清除设计器的设置AutoScaleDimensions,从而导致实际切换自动缩放!(此指南与先前的指南相结合意味着您永远无法在设计器中实例化基类……所有类都必须设计为基类或叶类!)

  • 避免Form.MaxSize在Designer中静态使用/。MinSizeMaxSize上形不成规模不亚于一切。因此,如果您以96dpi的速度完成所有工作,则在较高DPI MinSize时不会引起问题,但可能不会像您预期的那样受到限制,但是MaxSize可能会限制Size的缩放比例,从而导致问题。如果你想MinSize == Size == MaxSize,不这样做,在设计......这样做,在你的构造或OnLoad覆盖......一套既MinSizeMaxSize您正确缩放尺寸。

  • 特定控件上的所有控件PanelContainer应使用“锚定”或“停靠”。如果将它们混合使用,那么这样做的自动缩放Panel通常会以微妙的怪异方式出现。

  • 当它进行自动缩放时,它将尝试缩放整个窗体...但是,如果在此过程中遇到了屏幕尺寸的上限,则这是一个硬上限,然后可以拧紧(剪辑)缩放比例。因此,您应该确保设计器中所有100%/ 96dpi的窗体的大小都不会大于1024x720(相当于1080p屏幕上的150%或4K屏幕上Windows的建议值300%)。但是您需要减去巨大的Win10标题/标题栏...所以更像是1000x680最大尺寸...在设计器中它将像是994x642 ClientSize。(因此,您可以对ClientSize进行FindAll引用以查找违规者。)


NumericUpDown也无法Margin正确缩放。看来保证金被扩大了两倍。如果我将其缩小一次,则看起来不错。
ygoe

AutoScaleMode = Font对于在Ubuntu上使用非常大的字体的用户来说效果不佳。我们更喜欢AutoScaleMode = DPI
KindDragon

>具有MultiLine = True且继承了Font的TextBox。整日发疯-这就是解决方法!非常感谢!顺便说一句,相同的修补程序也是ListBox控件的修补程序。:D
neminem '17

对我来说,具有继承字体的列表框无法很好地缩放。他们在显式设置后执行。(.NET 4.7)
PulseJet


27

我的经验与当前获得最高投票的答案大不相同。通过逐步阅读.NET框架代码并仔细阅读参考源代码,我得出结论,所有东西都可以自动缩放正常工作,并且在将它弄乱的地方只有一个微妙的问题。事实证明这是事实。

如果您创建了一个可重排/自动调整大小的布局,则几乎所有内容都会自动运行,并使用Visual Studio使用的默认设置(即,父窗体上的AutoSizeMode = Font,其他上的Inherit)。

唯一的难题是,如果您在设计器中的窗体上设置了Font属性。生成的代码将按字母顺序对分配进行排序,这意味着AutoScaleDimensions将在之前 分配Font。不幸的是,这完全破坏了WinForms自动缩放逻辑。

解决方法很简单。要么根本不用Font在设计器中设置属性(在表单构造器中设置属性),要么手动对这些分配进行重新排序(但是每次您在设计器中编辑表单时,都必须继续这样做)。Voila,几乎完美且全自动的缩放比例,麻烦最少。即使表单大小也可以正确缩放。


我遇到的问题将在这里列出:

  • Nested TableLayoutPanel 错误地计算了控制边距。没有已知的解决方法,只能完全避免边距和填充-或避免嵌套表布局面板。

1
不要Font在设计器中进行设置:想到一个想法:继续在设计器中设置字体,以便可以使用所需的字体进行设计。然后在构造函数中进行布局后,读取该字体属性并再次设置相同的值?还是只是要求再次进行布局?[注意:我没有理由测试这种方法。]或按照Knowleech的回答,在设计器中以像素为单位指定(这样Visual Studio设计器将不会在高DPI监视器上重新缩放),并在代码中读取该值,然后从像素转换点(以获得正确的缩放比例)。
ToolmakerSteve

1
我们代码的每一位都在自动缩放模式之前设置了自动缩放尺寸,并且都可以完美缩放。在大多数情况下,顺序似乎无关紧要。
乔什

我在代码中搜索了AutoScaleDimensions未设置为new SizeF(6F, 13F)最佳答案中所建议的的实例。事实证明,在每种情况下,都设置了表单的Font属性(不是默认值)。看来,当时AutoScaleMode = Font,则AutoScaleDimensions根据表单的font属性进行计算。另外,Windows控制面板中缩放比例设置似乎也会对产生影响。AutoScaleDimensions
Walter Stabosz '19

24

将您的.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和动态DPI方案的增强功能。这些包括:

  • 改进了许多Windows Forms控件的缩放和布局,例如MonthCalendar控件和CheckedListBox控件。

  • 单遍缩放。在.NET Framework 4.6和更早版本中,缩放是通过多次传递来执行的,这导致某些控件的缩放超出了必要。

  • 支持动态DPI方案,在这种情况下,用户在启动Windows Forms应用程序后更改DPI或比例因子。

要支持它,请将应用程序清单添加到您的应用程序中,并表明您的应用程序支持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> 

自Windows 10 Creators更新以来,此PerMonitorV2是新的:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

也称为Per Monitor v2。在原始的按显示器DPI感知模式上进行了改进,该模式使应用程序可以在每个顶级窗口的基础上访问与DPI相关的新缩放行为。

  • 子窗口DPI更改通知 -在Per Monitor v2上下文中,会向整个窗口树通知发生的任何DPI更改。

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

  • 小号caling Win32的菜单 -在每个监视器V2上下文创建的所有NTUSER菜单将在每个显示器的方式缩放。

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

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

  • 改进的主题行为 -在Per Monitor v2窗口的上下文中打开的UxTheme句柄将根据与该窗口关联的DPI进行操作。

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

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

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

  • Form.DpiChanged,当DPI设置在当前显示表单的显示设备上更改时触发。

您还拥有3种有关DPI处理/扩展的帮助方法:

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

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

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

如果仍然发现问题,则可以通过app.config条目选择退出DPI改进

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

在此处输入图片说明

这会激活GDI缩放比例,从而改善DPI处理:

对于基于GDI的应用程序,Windows现在可以DPI基于每个监视器进行缩放。这意味着,这些应用程序将神奇地成为每个监视器的DPI感知。

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


12

我在工作中写的指南:

WPF以“设备独立单位”工作,这意味着所有控件都可以完美缩放到高dpi屏幕。在WinForms中,它需要多加注意。

WinForms以像素为单位。文本将根据系统dpi缩放,但通常会通过未缩放的控件进行裁剪。为避免此类问题,您必须避免显式调整大小和位置。请遵循以下规则:

  1. 无论在哪里找到它(标签,按钮,面板),都将AutoSize属性设置为True。
  2. 对于布局,请使用FlowLayoutPanel(大约WPF StackPanel)和TableLayoutPanel(大约WPF Grid)进行布局,而不要使用Vanilla Panel。
  3. 如果您在高dpi的计算机上进行开发,则Visual Studio设计人员可能会感到沮丧。当您设置AutoSize = True时,它将在屏幕上调整控件的大小。如果控件的AutoSizeMode = GrowOnly,则对于使用普通dpi的人(例如,),它将保持此大小。比预期的要大。要解决此问题,请在具有正常dpi的计算机上打开设计器,然后右键单击并重置。

3
对于可以自动调整大小的对话框,这是一场噩梦,我不希望按钮越来越大,因为我在运行程序时手动增加对话框的大小。
乔什

10

我发现要使WinForms在高DPI上表现出色是非常困难的。因此,我编写了一个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

6

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

只是我的两分钱。我以为我可以分享,因为在Internet上找不到“保持AutoScaleMode = Font”“为Designer设置以像素为单位的字体大小”

我的博客上有更多详细信息:http : //www.sgrottel.de/? p = 1581& lang= zh- CN


4

除了锚点不能很好地工作之外:我还要走得更远,说准确的定位(也就是使用Location属性)在字体缩放方面不能很好地工作。我不得不在两个不同的项目中解决这个问题。在这两种方法中,我们都必须将所有WinForms控件的位置转换为使用TableLayoutPanel和FlowLayoutPanel。使用TableLayoutPanel中的Dock(通常设置为Fill)属性非常有效,并且可以使用系统字体DPI很好地缩放。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.