首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

VC++ 自適應對話方塊佈局類 ClxDialog:控制項隨視窗縮放自動調整位置與大小

     編輯:VC 教學

痛點:OnSize 手寫座標計算的噩夢

對話方塊可調整大小 (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+)
};

規則語意範例

iFlagiPercent行為說明
MOVEX100對話框寬度增加 100px → 控制項 X 座標 +100px (右對齊)
MOVEY100對話框高度增加 100px → 控制項 Y 座標 +100px (底對齊)
ELASTICX100對話框寬度 +100px → 控制項寬度 +100px (填滿寬度)
ELASTICY50對話框高度 +100px → 控制項高度 +50px (半比例)
MOVEX50置中效果:對話框寬度變化一半傳遞給 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;
    }
};

ClxDialog 核心實作原理

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_cyMin
  • 巢狀佈局:GroupBox/TabControl 內控制項需相對父容器計算,可擴充規則加入 iParentId 欄位
  • DPI 感知:高 DPI 下 dx/dy 需轉換為邏輯單位,或啟用 Per-Monitor DPI Aware
  • 儲存/還原視窗尺寸:OnDestroy 寫入註冊表/INI,OnInitDialog 讀取並 SetWindowPos

對比現有方案

將 ClxDialog.h/cpp 加入專案,繼承它並宣告規則陣列,即可擁有專業級自適應對話方塊佈局能力,徹底告別 OnSize 手算座標的時代。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方案優點缺點
手寫 OnSize完全控制代碼重複、難維護、易出錯
MFC CResizeDialog (CodeProject)功能豐富依賴外部庫、學習成本高
ClxDialog (本方案)聲明式、零依賴、輕量、易擴充極複雜佈局需擴充規則引擎
WTL CDialogResize模板元編程、效能極高需使用 WTL 框架