代码改变世界

[自定义Component]WinForm的AutoScale能力

2007-10-20 13:46 by Colin Han, ... 阅读, ... 评论, 收藏, 编辑

 

在.NET 2.0中,微软对.NET 1.0中提出的WinForm的AutoScale能力进行了调整和增强。但是,微软始终没有跳出快速开发的圈子,因此,AutoScale做的并不彻底。其中有些工作还必须我们自己完成。

1. 什么是AutoScale?

我们知道,在不同的操作系统版本或语言版本之间,系统的默认字体是不同的。Window还提供了用户设置DPI的能力。因此,很多时候,我们在特定版本和DPI下设计的窗体,在另外一个操作系统下就会有一些字或图片被裁掉。如下图:

.NET提供了一套关于这个问题的解决方案。就是WinForm上的一组关于AutoScale的方法或属性。包括:

ContianerControl上有:

AutoScaleFactor property

AutoScaleDimensions property

CurrentAutoScaleDimensions property

AutoScaleMode property

PerformAutoScale method

Control上有:

Scale method

ScaleChildren method

GetScaledBounds method

一般情况下,微软会自动帮助你完成所有关于AutoScale的功能。你并不需要写特别的代码使用这些功能。

2.  AutoScale的实现细节

在DesignTime,系统会根据AutoScaleMode的设置不同,将一个参考值赋值给Form的AutoScaleDimensions属性。

例如:如果你选择AutoScaleMode为Font。DesignTime会自动为Form生成一行如下的代码来保存你设计时所使用的默认字体的宽和高:

this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 

Note: 当前系统为英文。因此,当前字体高度为13,宽度为6 

在运行时,CurrentAutoScaleDimensions属性会提取当前系统的相关设置。生成另外一个计算值。AutoScaleFactor属性表现了两个值的比值,作为下一步缩放的缩放因子。
比如,在日文系统下运行时,CurrenAutoScaleDimensions为(6f, 12f)。AutoScaleFactor为(1f, 0.9xxxf).

System.Win.Forms.ContainerControl会在OnLayout(还包括一堆其它的事件中)中对调用自己的 PerformAutoScale方法,其中对自己和它子孙调用Scale方法,并将AutoScaleFactor传入。从而实现调整他们的位置和大小,以适应当前操作系统的设置。

3. 使用AutoScale时需要注意的问题?

上面的实现应该说比较巧妙,加上Visual Studio的设计时支持。应该说已经可以完美的解决所有通过DesignTime设计的窗体或自定义控件。

但是,微软没有为这套系统进行完整的支持。如果你通过代码在运行时修改控件的位置或大小时,可以说你没有任何一个理想的方案来确保你的修改能够适应各种不同的操作系统设置。因为......当ContainerControl触发了PerformAutoScale方法后,为了确保下一次触发 PerformAutoScale方法不会累加操作,而将AutoScaleDimensions修改为和 CurrentAutoScaleDimensions一致。也就是说,你再也无法拿到设计时所使用的AutoScaleDimensions值。因此,你也就无法使用这套机制来确保你的修改操作可以兼容各种操作系统的设置。

在我们的产品中就有很多问题是这一点引起的。毕竟,在大多数产品中,并不会仅仅通过DesignTime托托拽拽就可以完成需求。因此,我们需要:

3.1 尽量使用DesignTime设置Control的Size和Location
3.2 如果不能使用DesignTime,自己需要维护AutoScaleDimensions值

看起来,我们只能自己手动生成我们自己的AutoScaleDimensions值。VS会将那一行代码生成在 InitializeComponents方法内部,该函数推出的时候,PerformAutoScale已经被调用,因此标准的 AutoScaleDimensions属性已经不能使用了。这里需要Hard Code。 // TODO: 谁有更好的建议?

并且在下列情况中手动对Control的Location或Size进行计算。

  • 修改Control的位置或大小时,手动计算正确的值。
  • 添加新Control到Controls之前,手动计算正确的Size和Location. 

我们需要使用前面我们自己维护的AutoScaleDimensions和当前容器的AutoScaleDimensions运算生成 AutoScaleFactor,然后自己计算新的位置和大小。这里,需要注意,不要参考Control的当前Size或Location。因为他们可能已经被Scale过一次了。

Note: 千万不要试图通过调用Control.Scale方法来完成计算。因为,ContainerControl的Scale方法会对所有子Control进行 Scale。但是,往往ContainerControl已经在它的构造函数中对它的子进行了Scale操作。结果相当于它的子进行了两次Scale。结果是错误的。