在 VC 6.0 開發中,對話框是最常用的 UI 形式之一。然而,VC 6.0 對執行期控制項佈局的支援極為有限:當使用者調整對話框大小,控制項不會自動適應,開發者必須手寫大量重複、枯燥的代碼來處理位置與大小的變化。這不僅增加維護成本,也讓程式碼顯得雜亂無章。
曾見有人利用巨集實現控制項隨對話框縮放,但缺乏類似單一文件介面(SDI)分割條的拖曳調整效果。為此,我設計並實作了一套完整的佈局管理器——DynamicLayout(動態佈局),支援面板巢狀、分割條拖曳、最大/最小尺寸限制等功能,極大簡化動態 UI 的開發。
以下截圖展示同一程式在不同階段的運行效果,請留意控制項隨視窗變化的行為:

圖 1 VC 6.0 對話框程式原始運行效果

圖 2 在圖 1 基礎上加入 DynamicLayout 管理

圖 3 在圖 2 基礎上改變對話框大小

圖 4 在圖 3 基礎上將分割條 A 移動到最左側

圖 5 正在移動中的分隔條 C(截圖時游標被隱藏)

圖 6 程式運行時的另一種佈局狀態
要在普通對話框中啟用 DynamicLayout,只需三個步驟:
CDynamicLayout 成員變數。OnInitDialog() 中呼叫介面註冊控制項,並設定面板、分割條等佈局規則。WindowProc(),將訊息轉發給 FilterDynamicLayoutMessage()。若以動態庫方式使用,還需對 VC 專案進行相應設定。以下以圖 1 所示對話框為例,實現圖 2~6 效果的核心代碼:
#include "DynamicLayoutExport.h" // 包含 DynamicLayout 標頭檔
/////////////////////////////////////////////////////////////////////////////
// CDynamicLayoutTestDlg 對話框
class CDynamicLayoutTestDlg : public CDialog
{
// ...
private:
DynamicLayout::CDynamicLayout m_DLayout; // 宣告動態佈局物件
};
BOOL CDynamicLayoutTestDlg::OnInitDialog()
{
// ... 原有初始化代碼 ...
m_DLayout.InitLayout(this);
m_DLayout.SetDialogMaxSize(800, 600); // 限制對話框最大 800x600
m_DLayout.SetDialogMinSize(200, 100); // 限制最小 200x100
// 建立面板結構:參數 1,1,1,1 表示上下左右皆鎖定父佈局邊框
m_DLayout.AddPanel(_T("上"), _T(""), 1, 1, 1, 1);
m_DLayout.AddPanel(_T("下"), _T(""), 0, 1, 1, 1);
m_DLayout.AddPanel(_T("上右"), _T("上"), 1, 1, 1, 1); // 加入子面板
m_DLayout.AddControl(IDC_LIST_LIST, _T("上右"), 1, 1, 1, 1);
m_DLayout.AddPanel(_T("上右下"), _T("上右"), 0, 1, 1, 0);
m_DLayout.AddControl(IDC_BTN_MODAL, _T("上右下"), 1, 1, 1, 0);
m_DLayout.AddControl(IDC_BTN_MODALNESS, _T("上右下"), 1, 1, 1, 0);
m_DLayout.AddControl(IDOK, _T("上右下"), 1, 1, 1, 0);
m_DLayout.AddPanel(_T("上左"), _T("上"), 1, 1, 1, 0);
m_DLayout.AddControl(IDC_TREE1, _T("上左"), 1, 1, 1, 1);
m_DLayout.AddPanel(_T("下左"), _T("下"), 1, 1, 1, 0);
m_DLayout.AddControl(IDC_LIST2, _T("下左"), 1, 1, 1, 1);
m_DLayout.AddPanel(_T("下右"), _T("下"), 1, 1, 1, 1);
m_DLayout.AddControl(IDC_EDIT1, _T("下右"), 1, 1, 1, 1);
m_DLayout.AddControl(IDC_STATIC_COMMAND, _T("下右"), 1, 0, 1, 0);
m_DLayout.AddControl(IDC_EDIT2, _T("下右"), 1, 0, 1, 1);
// 設定面板背景色
m_DLayout.SetBkColor(_T("上"), RGB(160, 160, 255));
m_DLayout.SetBkColor(_T("上右"), RGB(128, 128, 255));
m_DLayout.SetBkColor(_T("上右下"), RGB(128, 255, 128));
m_DLayout.ShowPanel(_T(""), TRUE); // 顯示根面板(見圖 2)
m_DLayout.SetSplit(_T("")); // 設定分割條 B(參看圖 4)
m_DLayout.SetSplit(_T("上")); // 設定分割條 A
m_DLayout.SetSplit(_T("下")); // 設定分割條 C
return TRUE;
}
LRESULT CDynamicLayoutTestDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
m_DLayout.FilterDynamicLayoutMessage(message, wParam, lParam); // 讓 CDynamicLayout 參與訊息處理
return CDialog::WindowProc(message, wParam, lParam);
}
DynamicLayout 的設計靈感來源於 Microsoft .NET 平台的佈局機制,特別是 Panel 概念;熟悉 .NET 的開發者定會感到親切。本專案僅實作核心功能,命名或嫌大膽,請多包涵。DynamicLayout 專案與測試專案均在 VC 6.0 下除錯通過,若有問題歡迎指正交流。