在開發可調整大小的對話框時,若控件眾多,在 OnSize 中逐一計算座標極其繁瑕。本文提供一個可重複使用的 ClxDialog 類別,只需在 OnInitDialog 中以資料驅動方式宣告控件的錨點與彈性規則,即可自動處理控件的位置與大小隨對話框縮放而變化。
| 旗標 | 值 | 行為說明 |
|---|---|---|
| MOVEX | 0 | 水平移動 |
| MOVEY | 1 | 垂直移動 |
| MOVEXY | 2 | 水平與垂直同時移動 |
| ELASTICX | 3 | 寬度隨對話框寬度變化 |
| ELASTICY | 4 | 高度隨對話框高度變化 |
| ELASTICXY | 5 | 寬高同時隨對話框變化 |
表示控件變化量佔對話框客戶區變化量的百分比 (0~100)。例如 iPercent=100, iFlag=MOVEX 表示對話框寬度每變化 1 單位,控件水平移動 1 單位;iPercent=50, iFlag=ELASTICX 表示控件寬度變化量為對話框寬度變化量的一半。
// 在 OnInitDialog 中設定
static DLGCTLINFO dcMenuGroup[] =
{
{IDOK, MOVEX, 100}, // 靠右對齊
{IDCANCEL, MOVEX, 100}, // 靠右對齊
{IDC_BUTTON1, MOVEX, 50}, // 水平置中
{IDC_BUTTON1, MOVEY, 100}, // 靠底對齊
{IDC_EDIT1, ELASTICX, 100}, // 寬度隨對話框全幅變化
{IDC_EDIT2, ELASTICX, 50}, // 寬度隨對話框半幅變化
{IDC_EDIT3, ELASTICX, 50},
{IDC_EDIT3, MOVEX, 50}, // 同一控件可設定多條規則
{IDC_EDIT4, ELASTICY, 100}, // 高度隨對話框全幅變化
{IDC_EDIT5, ELASTICX, 100},
{IDC_EDIT5, ELASTICY, 50},
{IDC_EDIT6, ELASTICX, 100},
{IDC_EDIT6, ELASTICY, 50},
{IDC_EDIT6, MOVEY, 50},
};
SetControlProperty(dcMenuGroup, sizeof(dcMenuGroup)/sizeof(DLGCTLINFO));
CDialog,覆寫 OnInitDialog、OnSize、OnSizing。m_iClientWidth/m_iClientHeight)、最小尺寸限制 (m_iMinWidth/m_iMinHeight)、控件規則陣列指標 (m_pControlArray)、規則數量 (m_iControlNumber)、尺寸圖示顯示旗標 (m_bShowSizeIcon) 等。SetControlProperty:注入控件規則陣列。ShowSizeIcon:控制右下角尺寸調整圖示顯示。OnInitDialog:啟用 WS_SIZEBOX、記錄初始尺寸、建立尺寸圖示靜態控件。OnSize:依規則計算增量並呼叫 MoveWindow 調整每個控件,最後移動尺寸圖示至右下角。OnSizing:限制對話框不得小於初始尺寸。class ClxDialog : public CDialog
{
public:
ClxDialog(UINT nID, CWnd* pParent = NULL);
typedef struct _dlgControlTag { int iId; int iFlag; int iPercent; } DLGCTLINFO, *PDLGCTLINFO;
enum { MOVEX=0, MOVEY, MOVEXY, ELASTICX, ELASTICY, ELASTICXY };
BOOL SetControlProperty(PDLGCTLINFO lp, int nElements);
void ShowSizeIcon(BOOL bShow = TRUE);
protected:
virtual BOOL OnInitDialog();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnSizing(UINT nSide, LPRECT lpRect);
DECLARE_MESSAGE_MAP()
private:
int m_iClientWidth, m_iClientHeight, m_iMinWidth, m_iMinHeight;
PDLGCTLINFO m_pControlArray; int m_iControlNumber; BOOL m_bShowSizeIcon;
CStatic m_wndSizeIcon; CBitmap m_bmpSizeIcon; BITMAP m_bitmap;
};
BOOL ClxDialog::OnInitDialog()
{
CDialog::OnInitDialog();
ModifyStyle(0, WS_SIZEBOX); // 允許調整大小
CRect rectDlg; GetWindowRect(rectDlg);
m_iMinWidth = rectDlg.Width(); m_iMinHeight = rectDlg.Height();
CRect rectClient; GetClientRect(rectClient);
m_iClientWidth = rectClient.Width(); m_iClientHeight = rectClient.Height();
// 載入系統 OBM_SIZE 圖示並建立右下角靜態控件
m_bmpSizeIcon.LoadOEMBitmap(OBM_SIZE);
m_bmpSizeIcon.GetBitmap(&m_bitmap);
m_wndSizeIcon.Create(NULL, WS_CHILD|WS_VISIBLE|SS_BITMAP,
CRect(0,0,m_bitmap.bmWidth,m_bitmap.bmHeight), this, 0);
m_wndSizeIcon.SetBitmap(m_bmpSizeIcon);
m_wndSizeIcon.MoveWindow(m_iClientWidth-m_bitmap.bmWidth, m_iClientHeight-m_bitmap.bmHeight,
m_bitmap.bmWidth, m_bitmap.bmHeight);
m_wndSizeIcon.ShowWindow(m_bShowSizeIcon);
return TRUE;
}
void ClxDialog::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
int iIncrementX = cx - m_iClientWidth;
int iIncrementY = cy - m_iClientHeight;
if (nType == SIZE_MINIMIZED) iIncrementX = iIncrementY = 0;
for (int i=0; i<m_iControlNumber; ++i)
{
// 取得控件指標、旗標、百分比
// 依旗標計算新 left/top/width/height
// 呼叫 pWndCtrl->MoveWindow(...)
}
// 移動尺寸圖示至新右下角
if (IsWindow(m_wndSizeIcon.GetSafeHwnd()))
m_wndSizeIcon.MoveWindow(cx-m_bitmap.bmWidth, cy-m_bitmap.bmHeight, m_bitmap.bmWidth, m_bitmap.bmHeight);
if (nType != SIZE_MINIMIZED) { m_iClientWidth = cx; m_iClientHeight = cy; }
}
void ClxDialog::OnSizing(UINT nSide, LPRECT lpRect)
{
CDialog::OnSizing(nSide, lpRect);
int w = lpRect->right - lpRect->left;
int h = lpRect->bottom - lpRect->top;
if (w <= m_iMinWidth) lpRect->right = lpRect->left + m_iMinWidth;
if (h <= m_iMinHeight) lpRect->bottom = lpRect->top + m_iMinHeight;
}

兩張截圖對比直觀呈現控件隨對話框縮放而自動重新佈局的效果。
ClxDialog 以資料驅動方式封裝了對話框控件自適應邏輯,開發者只需宣告規則陣列即可輕鬆實現複雜的響應式佈局,大幅減少重複程式碼,提升維護性。