WinForm 內建 Anchor、Dock、TableLayoutPanel、FlowLayoutPanel 等佈局機制,大多數情況已足夠。但若需「所有控制項依比例縮放(含字體大小)」或「巢狀容器遞迴調整」,可參考本文實作:在 Form.Load 記錄初始狀態,SizeChanged 依比例重新計算位置與尺寸。
核心邏輯
Form.Load:遞迴遍歷所有控制項,記錄 Location、Size、表單初始 ClientSize。
Form.SizeChanged:計算寬高縮放比 scaleX = CurrentWidth / InitialWidth、scaleY = CurrentHeight / InitialHeight。
- 遞迴套用:
control.Location = new Point((int)(origX * scaleX), (int)(origY * scaleY));control.Size = new Size((int)(origW * scaleX), (int)(origH * scaleY))。
- 縮小時啟用
AutoScroll,避免控制項疊加。
完整程式碼 (簡化版)
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
public class AutoScaleForm : Form
{
private readonly Dictionary _initialStates = new();
private Size _initialFormSize;
private bool _initialized;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_initialFormSize = ClientSize;
RecordInitialStates(this);
_initialized = true;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (!_initialized) return;
if (ClientSize.Width < _initialFormSize.Width || ClientSize.Height < _initialFormSize.Height)
{
AutoScroll = true;
RestoreInitialStates(this);
}
else
{
AutoScroll = false;
ApplyScaling(this);
}
}
private void RecordInitialStates(Control container)
{
foreach (Control c in container.Controls)
{
if (!_initialStates.ContainsKey(c))
_initialStates[c] = c.Bounds; // 記錄 Location + Size
if (c.HasChildren) RecordInitialStates(c);
}
}
private void RestoreInitialStates(Control container)
{
foreach (Control c in container.Controls)
{
if (_initialStates.TryGetValue(c, out var bounds))
c.Bounds = bounds;
if (c.HasChildren) RestoreInitialStates(c);
}
}
private void ApplyScaling(Control container)
{
float scaleX = (float)ClientSize.Width / _initialFormSize.Width;
float scaleY = (float)ClientSize.Height / _initialFormSize.Height;
foreach (Control c in container.Controls)
{
if (_initialStates.TryGetValue(c, out var orig))
{
c.Bounds = new Rectangle(
(int)(orig.X * scaleX), (int)(orig.Y * scaleY),
(int)(orig.Width * scaleX), (int)(orig.Height * scaleY));
}
if (c.HasChildren) ApplyScaling(c);
}
}
}
關鍵細節與改進建議
| 問題 | 改進方向 |
| 字體不縮放 | 同步縮放 Font = new Font(origFont.FontFamily, origFont.Size * Math.Min(scaleX, scaleY), origFont.Style) |
| 最小尺寸限制 | 設定 MinimumSize 或在縮放前檢查 scale < 0.5f 停止縮放 |
| 特定控制項不想縮放 | 加入 Tag = "NoScale" 判斷跳過 |
| 效能:頻繁 SizeChanged 觸發 | 使用 ResizeBegin/ResizeEnd 或 `Timer` 去抖動 |
| 高 DPI 模糊 | 專案啟用 Per-Monitor DPI Aware (app.manifest + `SetProcessDpiAwarenessContext`) |
何時使用此方案 vs 內建佈局