起因
使用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 窗體中的自動縮放自動縮放的執行過程
Windows 窗體現在使用下面的邏輯自動對窗體及其內容進行縮放:
設計時,每一個 ContainerControl 分別在 AutoScaleMode 和 AutoScaleDimensions 中記錄縮放模式和它的當前分辨率。
運行時,實際分辨率存儲在 CurrentAutoScaleDimensions 屬性中。AutoScaleFactor 屬性會動態計算運行時分辨率與設計時分辨率的比值。
當加載窗體時,如果 CurrentAutoScaleDimensions 和 AutoScaleDimensions 的值不同,則會調用 PerformAutoScale 方法對該控件及其子控件進行縮放。此方法會掛起布局並調用 Scale 方法執行實際縮放。然後,會更新 AutoScaleDimensions 值以避免累進縮放。
在下面的情況下還會自動調用 PerformAutoScale:
在縮放模式為 Font 時響應 OnFontChanged 事件。
當繼續執行容器控件的布局時檢測到 AutoScaleDimensions 或 AutoScaleMode 屬性發生更改。
與上面的情況類似,檢測到父 ContainerControl 正在被縮放。每個容器控件只負責使用自己的比例因子縮放自己的子控件,並不負責縮放其父容器中的控件。
子控件可以通過下面的若干方式修改其縮放行為:
可以重寫 ScaleChildren 屬性以確定是否應縮放其子控件。
可以重寫 GetScaledBounds 方法以調整要將控件縮放至的邊界,但不調整縮放邏輯。
可以重寫 ScaleControl 方法以更改當前控件的縮放邏輯。