**简介:**有很多评论说:“WinForms不能很好地自动缩放DPI/font设置;切换到WPF。”不过,我认为那是基于.NET 1.1;看起来他们在.NET2.0中实现自动缩放方面做得很好。至少根据我们目前的研究和测试。但是,如果你们中的一些人知道得更好,我们很乐意听到你的声音。(请不要费心争论我们应该切换到WPF…现在这不是一个选择)。
问题:
- 在WinForms中,什么不能正确地自动缩放,因此应该避免?
- 程序员在编写WinForms代码时应该遵循什么设计准则,以便它能够很好地自动扩展?
目前我们已经确定的设计指导原则:
参见下面的community wiki answer。
其中是否有不正确或不充分的地方?我们还应该采取什么其他的指导方针?还有其他需要避免的模式吗?对此,任何其他指导都将不胜感激。
8条答案
按热度按时间tktrz96b1#
不支持伸缩的控件:
Label
和AutoSize = False
。在控件上显式设置Font
,使其在“属性”窗口中以粗体显示。ListView
列宽不缩放。重写表单的ScaleControl
来执行此操作。参见this answerSplitContainer
的Panel1MinSize
、Panel2MinSize
和SplitterDistance
属性TextBox
,继承了MultiLine = True
和Font
。在控件上显式设置Font
,使其在“属性”窗口中以粗体显示。ToolStripButton
的图片。在窗体的构造函数中:ToolStrip.AutoSize = False
CreateGraphics.DpiX
和.DpiY
设置ToolStrip.ImageScalingSize
ToolStrip.AutoSize = True
。PictureBox.SizeMode
必须设置为缩放或拉伸图像。有时
AutoSize
可以保留在True
,但有时没有这些步骤就无法调整大小。在没有.NET Framework 4.5.2和EnableWindowsFormsHighDpiAutoResizing
的情况下工作。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
Right
或Bottom
...其定位不会自动缩放;而是将Panel或其他容器拖放到UserControl中,并将其他控件锚定到该Panel;让Panel在UserControl中使用DockRight
、Bottom
或Fill
。当
InitializeComponent
末尾的ResumeLayout
被调用时,只有Controls列表中的控件才会被自动缩放...如果动态添加控件,则在添加控件之前,需要对该控件执行SuspendLayout();
AutoScaleDimensions = new SizeF(6F, 13F);
AutoScaleMode = AutoScaleMode.Font;
ResumeLayout();
操作。如果您没有使用Dock模式或布局管理器(如FlowLayoutPanel
或TableLayoutPanel
),则还需要调整您的位置。从
ContainerControl
派生的基类应将AutoScaleMode
设置为Inherit
(类ContainerControl
中设置的默认值;但不是设计者设置的默认值)。如果你将它设置为其他任何值,然后你的派生类试图将它设置为Font(它应该这样做),那么将其设置为Font
的行为将清除设计器的AutoScaleDimensions
设置,导致实际上关闭自动缩放!(此准则与前一准则相结合意味着您永远不能在设计器中示例化基类……所有的类都需要被设计为基类或者叶子类!)避免在Designer中静态地/使用
Form.MaxSize
。Form上的MinSize
和MaxSize
不像其他所有内容那样缩放。因此,如果您在96 dpi下完成所有工作,那么在更高的DPI下,您的MinSize
不会导致问题,但可能不会像您预期的那样限制,但您的MaxSize
可能会限制您的Size缩放,这可能会导致问题。如果你想要MinSize == Size == MaxSize
,不要在设计器中这样做...在你的构造函数中或者OnLoad
覆盖...将MinSize
和MaxSize
设置为正确缩放的大小。特定
Panel
或Container
上的所有控件都应使用锚定或对接。如果你混合使用它们,Panel
所做的自动缩放通常会以微妙的奇怪方式表现不佳。当它自动缩放时,它将尝试缩放整个窗体……然而,如果在该过程中它运行到屏幕大小上限,那么这是一个硬限制,然后可以搞砸(剪辑)缩放。因此,您应该确保设计器中所有100%/96 dpi的窗体大小不大于1024 x720(对应于1080 p屏幕上的150%或4K屏幕上的Windows推荐值300%)。但是你需要减去巨大的Win10标题/标题栏。所以更像是1000 x680最大尺寸...在设计器中,它将是994 x642 ClientSize。(因此,您可以在ClientSize上执行FindAll References以查找违规者。
0mkxixxg2#
我的经历与目前的最高投票答案相当不同。通过单步执行.NET框架代码并仔细阅读参考源代码,我得出的结论是,自动缩放工作的一切都已就绪,只是在某个地方出现了一个细微的问题。事实证明,这是真的。
如果创建了一个正确的可重排/自动调整大小的布局,则几乎所有内容都将按照Visual Studio所使用的默认设置(即,父窗体上的AutoSizeMode = Font,其他所有内容上的Inherit)自动运行。
唯一的问题是,如果您已经在设计器中设置了窗体的Font属性。生成的代码将按字母顺序对赋值进行排序,这意味着
AutoScaleDimensions
将在Font
之前赋值。不幸的是,这完全破坏了WinForms的自动缩放逻辑。不过,修复方法很简单。要么根本不要在设计器中设置
Font
属性(在表单构造函数中设置),要么手动重新排序这些赋值(但是每次在设计器中编辑表单时都必须继续这样做)。瞧,几乎完美和全自动的缩放与最小的麻烦。甚至表单大小也可以正确缩放。我将在这里列出我遇到的已知问题:
TableLayoutPanel
calculates control margins incorrectly。除了完全避免边距和填充,或者避免嵌套的表格布局面板之外,没有已知的解决方法。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 10:
字符串
接下来,添加一个
app.config
并声明应用程序“Per Monitor Aware”(每监视器感知)。现在这是在app.config中完成的,而不是像以前那样在清单中!型
此PerMonitorV2是自Windows 10创建者更新后的新版本:
DPI_意识_上下文_个人_监视器_意识_V2
也称为每监视器v2。这是对原始的每监视器DPI感知模式的改进,使应用程序能够基于每个顶级窗口访问新的DPI相关缩放行为。
*子窗口DPI更改通知-在Per Monitor v2上下文中,将向整个窗口树通知发生的任何DPI更改。
*缩放非客户端区域-所有窗口的非客户端区域都将自动以DPI敏感方式绘制。不需要调用EnableNonClientDpiScaling。
*对话框缩放-在“每监视器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,我们可能会得到更多的改进。
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的计算机上打开设计器,然后单击鼠标右键,重置。
klh5stk15#
我发现很难让WinForms在高DPI下运行得很好。所以,我写了一个VB.NET方法来覆盖表单行为:
字符串
lndjwyie6#
我最近遇到了这个问题,特别是在高dpi系统上打开编辑器时与Visual Studio重新缩放结合使用时。我发现最好是保持
AutoScaleMode = Font
,但将FormsFont设置为默认字体,但指定大小为像素,而不是点,即:Font = MS Sans; 11px
,在代码中,我然后将字体重置为默认值:Font = SystemFonts.DefaultFont
一切都很好。只是我的两分钱。我想我分享,因为 “保持AutoScaleMode=字体”,和 “设置字体大小在像素的设计师” 是我没有找到在互联网上。
我在我的博客上有更多的细节:http://www.sgrottel.de/?p=1581&lang=en
unguejic7#
除了锚钉不能很好地工作之外:我会更进一步说,精确定位(也就是使用Location属性)对字体缩放不能很好地工作。我不得不在两个不同的项目中解决这个问题。在这两种情况下,我们都必须将所有WinForms控件的定位转换为使用TableLayoutPanel和FlowLayoutPanel。在TableLayoutPanel中使用Dock(通常设置为Fill)属性效果非常好,并且可以根据系统字体DPI进行良好的缩放。
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()时,我没有任何运气使它正确运行,无论我做什么,它们似乎都会导致布局混乱。