對話方塊可調整大小 (WS_THICKFRAME) 時,每個控制項都需在 OnSize 中手算新位置/大小。控制項一多、對話方塊一多,代碼量爆炸、維護極難。
定義控制項「如何跟隨對話方塊變化」的規則,基類 ClxDialog 自動計算並套用,業務對話方塊僅需在 OnInitDialog 宣告規則陣列。
// 佈局旗標
enum LayoutFlag {
MOVEX = 0, // 水平移動
MOVEY = 1, // 垂直移動
MOVEXY = 2, // 同時移動
ELASTICX = 3, // 寬度彈性縮放
ELASTICY = 4, // 高度彈性縮放
ELASTICXY = 5 // 寬高同時縮放
};
struct DLGCTLINFO {
int iId; // 控制項 ID
int iFlag; // LayoutFlag
int iPercent; // 變化比例 (0-100+)
};
| iFlag | iPercent | 行為說明 |
|---|---|---|
| MOVEX | 100 | 對話框寬度增加 100px → 控制項 X 座標 +100px (右對齊) |
| MOVEY | 100 | 對話框高度增加 100px → 控制項 Y 座標 +100px (底對齊) |
| ELASTICX | 100 | 對話框寬度 +100px → 控制項寬度 +100px (填滿寬度) |
| ELASTICY | 50 | 對話框高度 +100px → 控制項高度 +50px (半比例) |
| MOVEX | 50 | 置中效果:對話框寬度變化一半傳遞給 X 位移 |
class CMyDialog : public ClxDialog {
// ...
virtual BOOL OnInitDialog() override {
ClxDialog::OnInitDialog();
static DLGCTLINFO rules[] = {
{IDOK, MOVEX, 100}, // OK 鈕右對齊
{IDCANCEL, MOVEX, 100}, // Cancel 右對齊
{IDC_BTN1, MOVEX, 50}, // 按鈕置中
{IDC_BTN1, MOVEY, 100}, // 按鈕底對齊
{IDC_EDIT1, ELASTICX, 100}, // 編輯框填滿寬度
{IDC_EDIT2, ELASTICX, 50}, // 編輯框半比例寬度
{IDC_EDIT2, MOVEX, 50}, // 同時置中
{IDC_LIST1, ELASTICXY,100}, // 列表填滿客戶區
{IDC_TREE1, ELASTICY, 100}, // 樹狀圖填滿高度
{IDC_TREE1, MOVEX, 50}, // 樹狀圖水平置中
};
SetControlProperty(rules, _countof(rules));
ShowSizeIcon(TRUE); // 右下角顯示縮放圖示
return TRUE;
}
};
class ClxDialog : public CDialog {
protected:
int m_cxClient, m_cyClient; // 當前客戶區尺寸
int m_cxMin, m_cyMin; // 最小尺寸限制
DLGCTLINFO* m_pRules; // 規則陣列指標
int m_nRuleCount; // 規則數量
BOOL m_bShowSizeIcon;
CStatic m_wndSizeIcon; // 縮放圖示靜態控制項
CBitmap m_bmpSizeIcon;
// 初始化:記錄初始客戶區尺寸、載入縮放圖示
virtual BOOL OnInitDialog() override {
CDialog::OnInitDialog();
CRect rc; GetClientRect(&rc);
m_cxClient = rc.Width(); m_cyClient = rc.Height();
m_cxMin = m_cxClient; m_cyMin = m_cyClient; // 預設最小=初始
if (m_bShowSizeIcon) InitSizeIcon();
return TRUE;
}
// 尺寸變化核心邏輯
afx_msg void OnSize(UINT nType, int cx, int cy) {
CDialog::OnSize(nType, cx, cy);
if (nType == SIZE_MINIMIZED || !m_pRules) return;
int dx = cx - m_cxClient;
int dy = cy - m_cyClient;
m_cxClient = cx; m_cyClient = cy;
for (int i = 0; i < m_nRuleCount; ++i) {
ApplyRule(m_pRules[i], dx, dy);
}
RepositionSizeIcon();
}
void ApplyRule(const DLGCTLINFO& rule, int dx, int dy) {
CWnd* pCtrl = GetDlgItem(rule.iId);
if (!pCtrl) return;
CRect rc; pCtrl->GetWindowRect(&rc); ScreenToClient(&rc);
switch (rule.iFlag) {
case MOVEX: rc.OffsetRect(dx * rule.iPercent / 100, 0); break;
case MOVEY: rc.OffsetRect(0, dy * rule.iPercent / 100); break;
case MOVEXY: rc.OffsetRect(dx * rule.iPercent / 100, dy * rule.iPercent / 100); break;
case ELASTICX: rc.right += dx * rule.iPercent / 100; break;
case ELASTICY: rc.bottom += dy * rule.iPercent / 100; break;
case ELASTICXY:
rc.right += dx * rule.iPercent / 100;
rc.bottom += dy * rule.iPercent / 100; break;
}
pCtrl->MoveWindow(&rc);
}
// Sizing 限制最小尺寸
afx_msg void OnSizing(UINT nSide, LPRECT lpRect) {
CDialog::OnSizing(nSide, lpRect);
int cx = lpRect->right - lpRect->left;
int cy = lpRect->bottom - lpRect->top;
if (cx < m_cxMin) {
if (nSide & (WMSZ_LEFT | WMSZ_TOPLEFT | WMSZ_BOTTOMLEFT))
lpRect->left = lpRect->right - m_cxMin;
else
lpRect->right = lpRect->left + m_cxMin;
}
if (cy < m_cyMin) {
if (nSide & (WMSZ_TOP | WMSZ_TOPLEFT | WMSZ_TOPRIGHT))
lpRect->top = lpRect->bottom - m_cyMin;
else
lpRect->bottom = lpRect->top + m_cyMin;
}
}
// 縮放圖示處理 (OBM_SIZE 右下角標準圖示)
void InitSizeIcon() { /* 載入 OBM_SIZE 位圖到 m_wndSizeIcon */ }
void RepositionSizeIcon() { /* 移動圖示至右下角 */ }
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(ClxDialog, CDialog)
ON_WM_SIZE()
ON_WM_SIZING()
END_MESSAGE_MAP()
SetMinTrackSize(cx, cy) 設定 m_cxMin/m_cyMiniParentId 欄位dx/dy 需轉換為邏輯單位,或啟用 Per-Monitor DPI AwareOnDestroy 寫入註冊表/INI,OnInitDialog 讀取並 SetWindowPos| 方案 | 優點 | 缺點 |
|---|---|---|
| 手寫 OnSize | 完全控制 | 代碼重複、難維護、易出錯 |
| MFC CResizeDialog (CodeProject) | 功能豐富 | 依賴外部庫、學習成本高 |
| ClxDialog (本方案) | 聲明式、零依賴、輕量、易擴充 | 極複雜佈局需擴充規則引擎 |
| WTL CDialogResize | 模板元編程、效能極高 | 需使用 WTL 框架 |