Windows窗口自动缩放机制

时间:2021-10-28 01:01:27

通过自动缩放功能,能使在一个计算机上设计的界面在另一个具有不同分辨率或系统字体的计算机上能正常显示。这样窗体及其控件就能通过智能化调整大小以保障在本地电脑和用户电脑上保持一致。

自动缩放的必要性

如果没有自动缩放功能,当改变显示分辨率或字体时,其对应的应用不是显得太大,就是显得太小。例如,为Tahoma 9 point字体设计的程序,在系统字体为Tahoma 12 point的计算机上运行时,如果不对它进行任何调整,该应用程序就会显得很小。其呈现的文本 与其他应用程序相比都要小一些。此外,包含文本的UI元素的大小也与使用的字体相关,此时这些元素也会显得相对小些。

如果程序是针对某种分辨率设计的,也会发生类似情况。最常见的显示分辨率为96DPI(dots per inch),按时高分辨率的显示器如120、133、170也越来越多。如果应用不随着分辨率调整,大小显示也会不正常。

自动缩放即根据相对字体大小或分辨率来对窗体及其控件的大小进行调整。Windows操作系统能使用一种称为对话框单位(dialog units)的相对单位,对对话框进行自动缩放。对话框单位基于系统字体,它与像素的关系可以通过Win32 SDK的GetDialogBaseUnits函数确定。当用户改变了Windows使用的主题,所有的对话框都会自动调整对话框。另外,.NET框架能根据默认系统字体会显示分辨率自动缩放。当然,也可以禁用自动缩放。

最早对自动缩放的支持

.NET 1.0和1.1 采用一种很直接的方法实现自动缩放,该方法依赖于UI使用的Windows默认字体,该字体由Win32 SDK的DEFAULT_GUI_FONT表示。通常只有在显示分辨率改变时,该字体才会改变。下面的机制用于实现自动缩放:

  1. 设计时,AutoScaleBaseSize属性(已被废弃)被设置为本地电脑上默认系统字体的高度和宽度。
  2. 运行时,使用用户电脑上的默认系统字体初始化Form类的Font属性。
  3. 在窗体显示前,调用ApplyAutoScaling方法缩放form。该方法先计算AutoScaleBaseSize和Font的相对大小,然后调用Scale方法实现form及其组件的缩放。
  4. 因为AutoScaleBaseSize的值已经更新,所以之后调用ApplyAutoScaling不再调整窗口。

该机制可满足大部分需求,但它还有以下缺陷:

  • 因为AutoScaleBaseSize将baseline字体大小表示为整数值,所以某个窗体在经过多次分辨率更改后,其收入误差会越来越大。
  • 尽在Form类中实现了自动调整,而ContainerControl中没有。所以只有在用户控件与窗体在相同的分辨率下进行设计,并在设计阶段将控件放入窗体,用户控件才能正确缩放。
  • 只有在大家的计算机分辨率都相同的情况下,才能同时设计窗体及其子控件。并且还会使窗体的继承依赖于与父窗体关联的分辨率。
  • 和.NET 2.0中引入的布局管理器(FlowLayoutPanel和TableLayoutPanel)不兼容。
  • 不支持直接以与.NET 兼容框架所需的显示分辨率进行缩放。

所以,虽然该机制在.NET 2.0中保留以维护向后兼容性,但它已经被下面将要介绍的更可靠的机制所取代。因此,预期相关的类AutoScale, ApplyAutoScaling, AutoScaleBaseSize和某些Scale重载已过时。

现在对自动缩放的支持

在.NET 2.0中对Windows窗体的缩放的改进如下:

  • 对ContainerControl类的缩放提供了支持,这样forms, native composite控件以及自动以控件获得了同一的缩放支持。添加了AutoScaleFactor, AutoScaleDimensions, AutoScaleMode和PerformAutoScale类。
  • Control类添加了若干新成员,这些新成员用于辅助该类参与缩放并支持在同一窗体上进行混合缩放。具体地说,Scale, ScaleChildren, GetScaledBounds成员用于支持缩放。
  • 添加了建立在爱屏幕分辨率基础上的缩放支持,如AusoScaleMode枚举类所定义的。该模式与.NET Compact Framework的自动缩放兼容,从而更易于进行应用程序的迁移。
  • 自动缩放与FlowLayoutPanel和TableLayoutPanel等布局管理器兼容。
  • 缩放因子现在以浮点值表示,通常为SizeF类型,所以消除了舍入误差。

注意:还不支持DPI和字体混合模式缩放。虽然可以使用一种模式(如DPI)缩放某个控件,然后使用另一个模式(字体)将该控件放置在窗体上,这样做没有问题,但是某个模式下的基窗体和另一个模式下的派生窗体混合时,会导致不可预料的结果。

自动缩放实现

Windows窗体现在使用下面的逻辑实现自动缩放:

  1. 设计时,每个ContainerControl分别在AutoScaleMode和AutoScaleDimensions中记录缩放模式和当前分辨率
  2. 运行时,实际分辨率存储在CurrentAutoScaleDimensions属性中。AutoScaleFactor属性会动态计算运行时分辨率和设计时分辨率的比值。
  3. 加载窗体时,如果CurrentAutoScaleDimensions和AutoScaleDimensions的值不同,则会调用PerformAutoScale方法对该空间及其子控件进行缩放。该方法会挂起布局并调用Scale方法执行实际的缩放。之后,更新AutoScaleDimensions以避免累计缩放。
  4. 在下面的情况也会调用PerformAutoScale:
  • 如果缩放模式为Font,响应OnFontChanged事件。
  • 继续执行容器控件布局时检测到AutoScaleDimensions或AutoScaleMode属性发生改变。
  • 检测到父ContainerControl在被缩放。每个容器控件只负责使用自己的比例缩放自己的子控件,对父容器中的控件不负责。
  • 子控件可通过以下的方式修改其缩放行为:
    • 重写ScaleChildren属性以确定其子控件是否应缩放。
    • 重写GetScaledBounds方法以调整控件缩放的边界,但不调整缩放逻辑。
    • 重写ScaleControl方法更改当前控件的缩放逻辑。