起因
使用SmartPhone上的WinForm做了一个WM的小程序,结果放到手机上实际一运行。发现动态生成的控件在里面显示得都非常小,难以看清。
原因
我的问题是需要在InitializeComponent方法结束后,动态生成一些控件,如下:
/// <summary>
/// 这个方法会根据传入的实体模型,生成一些选择框,设置它们的大小、位置;并会改变其它控件的大小、位置。
/// </summary>
/// <param name="categories"></param>
private void GenerateCheckBoxes(IList<Category> categories)
{
……
}
原因就是因为手机分辨率较大,而这些动态生成的控件并没有进行随着分辨率不同而进行自动缩放。而由界面设计器设计出来的控件,都能很好的显示。
求索
由于界面生成的控件能够很好的自适应分辨率的不同,所以先看一下Designer生成的代码:
private void InitializeComponent()
{
this.BAdd = new System.Windows.Forms.Button();
this.PCategories = new System.Windows.Forms.Panel();
this.SuspendLayout();
// BAdd
this.BAdd.Location = new System.Drawing.Point(165, 164);
this.BAdd.Name = "BAdd";
this.BAdd.Size = new System.Drawing.Size(72, 20);
this.BAdd.TabIndex = 11;
this.BAdd.Text = "Add";
this.BAdd.Click += new System.EventHandler(this.BAdd_Click);
// PCategories
this.PCategories.Location = new System.Drawing.Point(73, 83);
this.PCategories.Name = "PCategories";
this.PCategories.Size = new System.Drawing.Size(164, 75);
// MainForm
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(243, 258);
this.Controls.Add(this.PCategories);
this.Controls.Add(this.BAdd);
this.Name = "MainForm";
this.Text = "MoneyManagerForm";
this.ResumeLayout(false);
}
这里的重点是使用了AutoScaleDimensions和AutoScaleMode属性来设置界面为自动缩放。(Dpi表示Dot per inch,WPF就是直接使用这种方式来控制界面的。)然后最后一步调用ResumeLayout方法,这个方法中,会调用到ContainerControl.PerformAutoScale方法进行自动缩放。
最可恶的一点:从控件的构造,到界面的自动缩放,全部在一个方法中实现!而且这个方法中,没有什么好的办法来调用我生成控件的方法……
解决过程
在Form中,重写ScaleControl方法如下:
protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
{
var categories = Config.Instance.Categories;
this.GenerateCheckBoxes(categories);
base.ScaleControl(factor, specified);
}
因为调用过程是这样的:
Control.LayoutResume() –> ContainerControl.PerformAutoScale() –> Control.Scale() –> Control.ScaleControl() & Control.ScaleChildControls()。
所以,只需要重写这个方法,就可以在真正执行自动缩放所有控件前,先把动态控件生成。
不过,这样做同样有局限性:因为这里是在InitializeComponent方法中进行PerformAutoScale,所以这里的这些动态生成的控件,其实是在应用程序的开始阶段就已经被明确了。相反,如果在运行一段时间后,需要想再动态生成其它控件,就不能使用这个方法了。那时,就需要直接调用刚生成的需要缩放的控件的Scale方法。而且不能直接使用PerformAutoScale方法了,因为要保证一个控件只被调用Scale方法一次! 在这里,只需要这样简单实现就行了。:)
另外,一开始以为PerformAutoScale并不会把缩放过的控件,再缩放一次,结果就写成了这样的错误方案:
public MainForm()
{
InitializeComponent();
//暂停布局
this.SuspendLayout();
//在InitComponents调用的PerformAutoScale方法里面,最后会把这个数给置为运行时的数据。所以这里需要重新设置。
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
//生成控件
this.GenerateCategories();
//自动缩放
this.PerformAutoScale();//其实这里会把InitializeComponent中缩放的控件都再缩放一遍。
//继续布局
this.ResumeLayout(false);
}
结束语
其实,这里的自动缩放过程,在WinForm开发中,也是一样的。
而且这次实践中,我还发现:我在Win7的系统上随手点了一下这个程序,居然所有功能都能够正常的运行……汗,当时做的时候,可是专门为WindowsMobile开发的窗体啊。这个“跨平台”功能,确实很强大,着实让我吃惊不小。
另外,由于VS2008自带的模拟器的屏幕分辨率和设计时的分辨率是一样大的,而我手机的分辨率比这个要大得多。所以每次调试这个缩放过程时,都要生成好了,然后拷贝到手机上看效果,真是吐血……
引用
Windows 窗体现在使用下面的逻辑自动对窗体及其内容进行缩放: