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

ubby3x7f  于 2023-08-07  发布在  其他
关注(0)|答案(8)|浏览(155)

**简介:**有很多评论说:“WinForms不能很好地自动缩放DPI/font设置;切换到WPF。”不过,我认为那是基于.NET 1.1;看起来他们在.NET2.0中实现自动缩放方面做得很好。至少根据我们目前的研究和测试。但是,如果你们中的一些人知道得更好,我们很乐意听到你的声音。(请不要费心争论我们应该切换到WPF…现在这不是一个选择)。

问题:

  • 在WinForms中,什么不能正确地自动缩放,因此应该避免?
  • 程序员在编写WinForms代码时应该遵循什么设计准则,以便它能够很好地自动扩展?

目前我们已经确定的设计指导原则:

参见下面的community wiki answer
其中是否有不正确或不充分的地方?我们还应该采取什么其他的指导方针?还有其他需要避免的模式吗?对此,任何其他指导都将不胜感激。

tktrz96b

tktrz96b1#

不支持伸缩的控件:

  • 继承了LabelAutoSize = False。在控件上显式设置Font,使其在“属性”窗口中以粗体显示。
  • ListView列宽不缩放。重写表单的ScaleControl来执行此操作。参见this answer
  • SplitContainerPanel1MinSizePanel2MinSizeSplitterDistance属性
  • TextBox,继承了MultiLine = TrueFont。在控件上显式设置Font,使其在“属性”窗口中以粗体显示。
  • ToolStripButton的图片。在窗体的构造函数中:
  • 设置ToolStrip.AutoSize = False
  • 根据CreateGraphics.DpiX.DpiY设置ToolStrip.ImageScalingSize
  • 如果需要,设置ToolStrip.AutoSize = True
  • PictureBox.SizeMode必须设置为缩放或拉伸图像。

有时AutoSize可以保留在True,但有时没有这些步骤就无法调整大小。在没有.NET Framework 4.5.2EnableWindowsFormsHighDpiAutoResizing的情况下工作。

  • TreeView的图片。根据CreateGraphics.DpiX.DpiY设置ImageList.ImageSize。对于StateImageList,可以在.NET Framework 4.5.1和EnableWindowsFormsHighDpiAutoResizing中不进行更改。
  • Form的大小。创建后手动缩放固定大小的Form

设计指南:

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

  • 所有ContainerControls也必须设置为相同的AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);,假设96 dpi(见下一个项目符号)和MS Sans Serif的默认字体(见下两个项目符号)。这是由设计器根据您打开设计器的DPI自动添加的...但在我们许多最古老的设计器文件中丢失了。也许Visual Studio .NET(VS 2005之前的版本)没有正确地添加它。

  • 做你所有的设计师工作在96 dpi(我们也许可以切换到120 dpi;但是网上的智慧说要坚持96 dpi;实验在那里是有序的;根据设计,这不应该有什么关系,因为它只是改变了设计人员插入的AutoScaleDimensions行)。要将Visual Studio设置为在高分辨率显示器上以虚拟96 dpi运行,请找到它的.exe文件,右键单击以编辑属性,然后在“兼容性”下选择“覆盖高DPI缩放行为”。缩放执行人:系统”。

  • 请确保您从未在容器级别设置字体...如果您想要一个应用程序范围的默认字体而不是MS Sans Serif,则只能在叶控件上或在最基本窗体的构造函数中使用。(在容器上设置字体似乎会关闭该容器的自动缩放,因为它按字母顺序排在AutoScaleMode和AutoScaleDimensions设置之后。)注意,如果您在最基本的Form的构造函数中更改字体,这将导致AutoScaleDimensions的计算与6x 13不同;特别是,如果你换成Segoe UI(Win 10默认字体),那么它将是7 X15...您需要触摸设计器中的每个窗体,以便它可以重新计算.designer文件中的所有维度,包括AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);

  • 请勿使用锚定到UserControl的Anchor RightBottom...其定位不会自动缩放;而是将Panel或其他容器拖放到UserControl中,并将其他控件锚定到该Panel;让Panel在UserControl中使用Dock RightBottomFill

  • InitializeComponent末尾的ResumeLayout被调用时,只有Controls列表中的控件才会被自动缩放...如果动态添加控件,则在添加控件之前,需要对该控件执行SuspendLayout();AutoScaleDimensions = new SizeF(6F, 13F);AutoScaleMode = AutoScaleMode.Font;ResumeLayout();操作。如果您没有使用Dock模式或布局管理器(如FlowLayoutPanelTableLayoutPanel),则还需要调整您的位置。

  • ContainerControl派生的基类应将AutoScaleMode设置为Inherit(类ContainerControl中设置的默认值;但不是设计者设置的默认值)。如果你将它设置为其他任何值,然后你的派生类试图将它设置为Font(它应该这样做),那么将其设置为Font的行为将清除设计器的AutoScaleDimensions设置,导致实际上关闭自动缩放!(此准则与前一准则相结合意味着您永远不能在设计器中示例化基类……所有的类都需要被设计为基类或者叶子类!)

  • 避免在Designer中静态地/使用Form.MaxSize。Form上的MinSizeMaxSize不像其他所有内容那样缩放。因此,如果您在96 dpi下完成所有工作,那么在更高的DPI下,您的MinSize不会导致问题,但可能不会像您预期的那样限制,但您的MaxSize可能会限制您的Size缩放,这可能会导致问题。如果你想要MinSize == Size == MaxSize,不要在设计器中这样做...在你的构造函数中或者OnLoad覆盖...将MinSizeMaxSize设置为正确缩放的大小。

  • 特定PanelContainer上的所有控件都应使用锚定或对接。如果你混合使用它们,Panel所做的自动缩放通常会以微妙的奇怪方式表现不佳。

  • 当它自动缩放时,它将尝试缩放整个窗体……然而,如果在该过程中它运行到屏幕大小上限,那么这是一个硬限制,然后可以搞砸(剪辑)缩放。因此,您应该确保设计器中所有100%/96 dpi的窗体大小不大于1024 x720(对应于1080 p屏幕上的150%或4K屏幕上的Windows推荐值300%)。但是你需要减去巨大的Win10标题/标题栏。所以更像是1000 x680最大尺寸...在设计器中,它将是994 x642 ClientSize。(因此,您可以在ClientSize上执行FindAll References以查找违规者。

0mkxixxg

0mkxixxg2#

我的经历与目前的最高投票答案相当不同。通过单步执行.NET框架代码并仔细阅读参考源代码,我得出的结论是,自动缩放工作的一切都已就绪,只是在某个地方出现了一个细微的问题。事实证明,这是真的。
如果创建了一个正确的可重排/自动调整大小的布局,则几乎所有内容都将按照Visual Studio所使用的默认设置(即,父窗体上的AutoSizeMode = Font,其他所有内容上的Inherit)自动运行。
唯一的问题是,如果您已经在设计器中设置了窗体的Font属性。生成的代码将按字母顺序对赋值进行排序,这意味着AutoScaleDimensions将在Font之前赋值。不幸的是,这完全破坏了WinForms的自动缩放逻辑。
不过,修复方法很简单。要么根本不要在设计器中设置Font属性(在表单构造函数中设置),要么手动重新排序这些赋值(但是每次在设计器中编辑表单时都必须继续这样做)。瞧,几乎完美和全自动的缩放与最小的麻烦。甚至表单大小也可以正确缩放。
我将在这里列出我遇到的已知问题:

vulvrdjw

vulvrdjw3#

将您的应用程序定位为.Net Framework 4.7并在Windows 10 v1703(创建者更新版本15063)下运行。用.Net 4.7 under Windows 10 (v1703), MS made a lot of DPI improvements
从.NET Framework 4.7开始,Windows Form包含一般高DPI和动态DPI案例的增强功能。其中包括:

  • 改进了许多Windows窗体控件(如MonthCalendar控件和CheckedListBox控件)的缩放比例和布局。
  • 单通道缩放。在.NET Framework 4.6和更早版本中,缩放是通过多次传递来执行的,这会导致某些控件的缩放幅度超过所需。
  • 支持动态DPI方案,在这些方案中,用户在Windows窗体应用程序启动后更改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并声明应用程序“Per Monitor Aware”(每监视器感知)。现在这是在app.config中完成的,而不是像以前那样在清单中!

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


PerMonitorV2是自Windows 10创建者更新后的新版本:

DPI_意识_上下文_个人_监视器_意识_V2

也称为每监视器v2。这是对原始的每监视器DPI感知模式的改进,使应用程序能够基于每个顶级窗口访问新的DPI相关缩放行为。

*子窗口DPI更改通知-在Per Monitor v2上下文中,将向整个窗口树通知发生的任何DPI更改。
*缩放非客户端区域-所有窗口的非客户端区域都将自动以DPI敏感方式绘制。不需要调用EnableNonClientDpiScaling。

      • 缩放Win32功能表**-在“每一监视器v2”内容中建立的所有NTUSER功能表,都将以每一监视器的方式进行缩放。
        *对话框缩放-在“每监视器v2”上下文中创建的Win32对话框将自动响应DPI更改。
        *改进了comctl 32控件的缩放-各种comctl 32控件改进了“每监视器v2”上下文中的DPI缩放行为。
        *改进的主题行为-在Per Monitor 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。

如果仍发现问题,可以执行opt-out of the DPI improvements via app.config entries
如果您无法访问源代码,则可以在Windows资源管理器中转到应用程序属性,转到兼容性并选择System (Enhanced)
x1c 0d1x的数据
这将激活GDI缩放以同时改进DPI处理:
对于基于GDI的应用程序,Windows现在可以在每个监视器的基础上对这些应用程序进行DPI扩展。这意味着,这些应用程序将神奇地成为每监视器DPI感知。
执行所有这些步骤,您应该会获得更好的DPI体验WinForms应用程序。但请记住,您需要针对.net 4. 7定位您的应用程序,并且至少需要Windows 10 Build 15063(创建者更新)。在下一个Windows 10更新1709,我们可能会得到更多的改进。

kuhbmx9i

kuhbmx9i4#

我在工作中写的一本书:
WPF工作在“设备独立单位”,这意味着所有控件都可以完美地缩放到高dpi屏幕。在WinForms中,它需要更多的关注。
WinForms以像素为单位工作。文本将根据系统dpi进行缩放,但通常会被未缩放的控件裁剪。要避免此类问题,必须避免显式调整大小和定位。请遵循以下规则:
1.无论在何处找到它(标签、按钮、面板),都将AutoSize属性设置为True。
1.对于布局,使用FlowLayoutPanel(一个WPF StackPanel)和TableLayoutPanel(一个WPF Grid)进行布局,而不是使用vanilla Panel。
1.如果您正在高dpi机器上进行开发,Visual Studio设计器可能会令人沮丧。当您设置AutoSize=True时,它将根据您的屏幕调整控件的大小。如果控件具有AutoSizeMode=GrowOnly,则对于正常dpi的人,它将保持此大小。比预期的要大。要解决此问题,请在具有正常dpi的计算机上打开设计器,然后单击鼠标右键,重置。

klh5stk1

klh5stk15#

我发现很难让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

字符串

lndjwyie

lndjwyie6#

我最近遇到了这个问题,特别是在高dpi系统上打开编辑器时与Visual Studio重新缩放结合使用时。我发现最好是保持AutoScaleMode = Font,但将FormsFont设置为默认字体,但指定大小为像素,而不是点,即:Font = MS Sans; 11px,在代码中,我然后将字体重置为默认值:Font = SystemFonts.DefaultFont一切都很好。
只是我的两分钱。我想我分享,因为 “保持AutoScaleMode=字体”,和 “设置字体大小在像素的设计师” 是我没有找到在互联网上。
我在我的博客上有更多的细节:http://www.sgrottel.de/?p=1581&lang=en

unguejic

unguejic7#

除了锚钉不能很好地工作之外:我会更进一步说,精确定位(也就是使用Location属性)对字体缩放不能很好地工作。我不得不在两个不同的项目中解决这个问题。在这两种情况下,我们都必须将所有WinForms控件的定位转换为使用TableLayoutPanel和FlowLayoutPanel。在TableLayoutPanel中使用Dock(通常设置为Fill)属性效果非常好,并且可以根据系统字体DPI进行良好的缩放。

whlutmcx

whlutmcx8#

我不得不检查并修复一大堆WinForms程序的缩放,至少有20个,由不同的人以不同的风格编写。大量的用户控件、拆分器、锚点、对接、面板、自定义控件、动态布局代码等。我做了很多实验,但我想我已经想出了一个很好的方法来处理它。
这个答案让我开始朝着正确的方向前进:Trying to make WinForms look good in 4K but forms too large after using AutoScaleMode.Dpi?
问题是,如果你有任何稍微复杂的东西,LayoutManager往往会破坏布局。调用SuspendLayout(),然后做一些事情,然后再调用ResumeLayout(),这确实是一个问题。(当您将用户控件与TabControl混合使用时,这也会对锚造成严重破坏。但这是另一个问题)。
关键是将窗体上的AutoScaleDimension和AutoScaleMode属性移动到SuspendLayout()/ResumeLayout()之外,这样在缩放之前所有内容都将正确布局。由于表单设计器会按照自己的意愿对语句进行排序,因此只需从.Designer.cs文件中删除这两行,并将它们移动到构造函数中InitializeComponent()方法的后面。
另一个重要的部分是将所有用户控件AutoScaleMode设置为Inherit,而不是font。这样,所有内容都可以一次缩放,而不是在用户控件中进行缩放,然后在添加到窗体时重新缩放。
在更改窗体上的AutoScaleMode之前,我递归地访问所有控件,以及任何没有停靠且具有除Top之外的锚点的控件|左,我暂时将锚定设置为顶部|左,然后在设置AutoScaleMode后将其恢复为原始值。
做这三件事让我大约90%的方式,几乎所有的工作都是自动的。总之,这三件事确保所有东西都被缩放一次,所有的一起,并达到相同的比例。任何偏离这种模式的行为似乎都会导致布局的混乱。
在应用程序开始时PInvoke user32.dll SetProcessDPIAware()也是一个好主意。这似乎允许程序化扩展甚至在150%的情况下工作。在设置SetProcessDpiAwareness()或SetProcessDpiAwarenessContext()时,我没有任何运气使它正确运行,无论我做什么,它们似乎都会导致布局混乱。

相关问题