Windows Mobile / WinForms 開發中,設計器生成的控制項能自動適應螢幕 DPI,但運行時動態建立的控制項卻顯示極小。本文剖析 AutoScale 機制,提供在 ScaleControl 重寫中注入動態控制項的最佳實踐。
InitializeComponent 結尾呼叫 ResumeLayout(false) → 觸發 PerformAutoScale() → ScaleControl() 縮放所有現有控制項。InitializeComponent 之後 建立,錯過縮放時機,保持設計時像素大小,高 DPI 裝置上極小難讀。this.AutoScaleDimensions = new SizeF(96F, 96F); // 設計時 DPI (96 DPI 基準)
this.AutoScaleMode = AutoScaleMode.Dpi; // 依 DPI 縮放
this.AutoScroll = true; // 內容超出自動捲動
執行時 CurrentAutoScaleDimensions 為實際 DPI,縮放因子 = 實際/設計時。
public Form1() {
InitializeComponent();
SuspendLayout();
AutoScaleDimensions = new SizeF(96F, 96F); // 重設設計時基準
GenerateControls(); // 建立動態控制項
PerformAutoScale(); // 錯誤:設計器控制項被二次縮放!
ResumeLayout(false);
}
PerformAutoScale 會對**所有**控制項(含已縮放的設計器控制項)再次縮放,導致疊加錯誤。
縮放調用鏈:LayoutResume → PerformAutoScale → Scale → ScaleControl → ScaleChildControls。
在 基類 ScaleControl 執行前 建立動態控制項,使其參與本次縮放:
protected override void ScaleControl(SizeF factor, BoundsSpecified specified) {
// 1. 先建立動態控制項(僅首次縮放時需建立)
if (!controlsGenerated) {
var categories = Config.Instance.Categories;
GenerateCheckBoxes(categories);
controlsGenerated = true;
}
// 2. 呼叫基類執行實際縮放(包含新建控制項)
base.ScaleControl(factor, specified);
}
關鍵點:
- 僅在首次縮放(表單載入)時建立動態控制項。
- 後續 DPI 變更(極少見)會再次觸發 ScaleControl,但 controlsGenerated 防止重複建立。
- 基類 ScaleControl 會遞迴縮放 Controls 集合內所有子控制項。
若遊戲運行中需新增控制項,不可再用 PerformAutoScale(會重複縮放舊控制項)。改為直接呼叫新控制項的 Scale(factor):
void AddDynamicControl(Control ctrl) {
ctrl.Scale(this.AutoScaleFactor); // 手動套用當前縮放因子
this.Controls.Add(ctrl);
}
同一套 WinForms 程式碼,**無修改即可在 Windows 7 桌面正常運行**。WinForms 的 DPI 感知機制自動處理桌面與行動裝置差異,驗證了 .NET 跨平台 UI 一致性。
Debug.WriteLine 輸出 AutoScaleFactor、控制項 Size 追蹤縮放過程。