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

MFC 程式設計師的 WTL 入門指南 (六):進階對話框 UI 類別——自繪、自訂繪製、新控件、UI 更新與 DDV

     編輯:關於VC++

進階對話框 UI 類別概覽

本章介紹 WTL 提供的進階 UI 功能類別:自繪/自訂繪製嵌入類別、WTL 獨有新控件、對話框控件 UI 更新、資料驗證 (DDV)、對話框動態調整大小。

自繪與自訂繪製嵌入類別

COwnerDraw:簡化 WM_DRAWITEM 等四大訊息處理

自繪控件需回應 WM_MEASUREITEM, WM_DRAWITEM, WM_COMPAREITEM, WM_DELETEITEM。COwnerDraw 封裝訊息分派,僅需覆寫虛擬函式。

// 訊息映射內部結構
BEGIN_MSG_MAP(COwnerDraw<T>)
    MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)       // 父視窗處理
    MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem)
    MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem)
    MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem)
    ALT_MSG_MAP(1)                                 // 反射訊息處理
    MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
    MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem)
    MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem)
    MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem)
END_MSG_MAP()

使用方式 A:父視窗處理 (不反射)

class CSomeDlg : public COwnerDraw<CSomeDlg>, ...
{
    BEGIN_MSG_MAP(CSomeDlg)
        CHAIN_MSG_MAP(COwnerDraw<CSomeDlg>)
    END_MSG_MAP()
    void DrawItem(LPDRAWITEMSTRUCT lpdis); // 覆寫此函式
};

使用方式 B:控件自我處理 (反射訊息)

class CSomeButtonImpl : public CWindowImpl<CSomeButtonImpl, CButton>,
                        public COwnerDraw<CSomeButtonImpl>
{
    BEGIN_MSG_MAP(CSomeButtonImpl)
        CHAIN_MSG_MAP_ALT(COwnerDraw<CSomeButtonImpl>, 1)
        DEFAULT_REFLECTION_HANDLER()
    END_MSG_MAP()
    void DrawItem(LPDRAWITEMSTRUCT lpdis);
};

可覆寫方法

void DrawItem(LPDRAWITEMSTRUCT);
void MeasureItem(LPMEASUREITEMSTRUCT);
int CompareItem(LPCOMPAREITEMSTRUCT);
void DeleteItem(LPDELETEITEMSTRUCT);

可呼叫 SetMsgHandled(FALSE) 讓訊息繼續傳遞。

實戰:自繪位圖按鈕 (CODButtonImpl)

void CODButtonImpl::DrawItem(LPDRAWITEMSTRUCT lpdis)
{
    CDCHandle dc = lpdis->hDC; CDC dcMem; dcMem.CreateCompatibleDC(dc);
    dc.SaveDC(); dcMem.SaveDC();
    // 背景:擁有焦點紅色,否則藍色
    if (lpdis->itemState & ODS_FOCUS) dc.FillSolidRect(&lpdis->rcItem, RGB(255,0,0));
    else dc.FillSolidRect(&lpdis->rcItem, RGB(0,0,255));
    // 位圖繪製:按下時偏移 1 像素
    dcMem.SelectBitmap(m_bmp);
    if (lpdis->itemState & ODS_SELECTED) dc.BitBlt(1,1,80,80,dcMem,0,0,SRCCOPY);
    else dc.BitBlt(0,0,80,80,dcMem,0,0,SRCCOPY);
    dcMem.RestoreDC(-1); dc.RestoreDC(-1);
}

自繪按鈕效果

CCustomDraw:處理 NM_CUSTOMDRAW 多階段自訂繪製

// 可覆寫的階段處理函式 (預設傳回 CDRF_DODEFAULT)
DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW);
DWORD OnPostPaint(int idCtrl, LPNMCUSTOMDRAW);
DWORD OnPreErase(int idCtrl, LPNMCUSTOMDRAW);
DWORD OnPostErase(int idCtrl, LPNMCUSTOMDRAW);
DWORD OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW);
DWORD OnItemPostPaint(int idCtrl, LPNMCUSTOMDRAW);
DWORD OnItemPreErase(int idCtrl, LPNMCUSTOMDRAW);
DWORD OnItemPostErase(int idCtrl, LPNMCUSTOMDRAW);
DWORD OnSubItemPrePaint(int idCtrl, LPNMCUSTOMDRAW);

範例:樹狀控件特定節點綠色顯示 (CBuffyTreeCtrl)

DWORD CBuffyTreeCtrl::OnPrePaint(int, LPNMCUSTOMDRAW) { return CDRF_NOTIFYITEMDRAW; }
DWORD CBuffyTreeCtrl::OnItemPrePaint(int, LPNMCUSTOMDRAW lpNMCD)
{
    if (1 == lpNMCD->lItemlParam) // 节點 item data = 1
        lpNMCD->clrText = RGB(0,128,0); // 綠色
    return CDRF_DODEFAULT;
}

WTL 獨有新控件

CBitmapButton:圖清單式位圖按鈕 (atlctrlx.h)

  • 使用 CImageList 而非單一位圖,減少 GDI 資源佔用 (對 Win9x 關鍵)。
  • 支援自動調整大小、3D 邊框、Hot-tracking、多狀態圖示 (正常/按下/焦點/停用)。

初始化代碼

CImageList iml;
iml.CreateFromImage(IDB_ALYSON_IMGLIST, 81, 1, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
m_wndBmpBtn.SubclassWindow(GetDlgItem(IDC_ALYSON_BMPBTN));
m_wndBmpBtn.SetToolTipText(_T("Alyson"));
m_wndBmpBtn.SetImageList(iml);
m_wndBmpBtn.SetImages(0, 1, 2, 3); // 正常、按下、焦點/游標、停用 索引

CBitmapButton 效果

擴展樣式旗標

旗標效果
BMPBTN_HOVERHot-tracking (游標移入顯示焦點狀態)
BMPBTN_AUTO3D_SINGLE/DOUBLE自動產生 3D 邊框 (DOUBLE 較粗)
BMPBTN_AUTOSIZE依圖示大小自動調整按鈕尺寸 (預設開啟)
BMPBTN_SHAREIMAGELISTS不銷毀共用 ImageList (預設銷毀)
BMPBTN_AUTOFIRE長按產生連續 WM_COMMAND

CCheckListViewCtrl:帶核取方塊的清單檢視

  • 衍生自 CWindowImpl,預設擴展樣式 LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT。
  • 可自訂 CCheckListViewCtrlImplTraits 指定視窗/擴展樣式。
  • 核心方法:SetCheckState(int index, bool), GetCheckState(int index), CheckSelectedItems(int index)。

CCheckListViewCtrl

CTreeViewCtrlEx / CTreeItem:樹狀控件操作簡化

  • CTreeItem 封裝 HTREEITEM + 控件指標,操作不需重複傳遞控件控制代碼。
  • CTreeViewCtrlEx 方法回傳 CTreeItem 而非 HTREEITEM。
// 使用 HTREEITEM
HTREEITEM hti = m_wndTree.InsertItem("foo", TVI_ROOT, TVI_LAST);
HTREEITEM hti2 = m_wndTree.InsertItem("bar", hti, TVI_LAST);
m_wndTree.SetItemData(hti2, 100);

// 使用 CTreeItem
CTreeItem ti = m_wndTreeEx.InsertItem("foo", TVI_ROOT, TVI_LAST);
CTreeItem ti2 = ti.AddTail("bar", 0);
ti2.SetData(100);

CHyperLink:超連結靜態文字控件

  • 子類化 Static 控件,依 IE 設定繪製連結顏色,支援鍵盤導航。
  • 方法:SetLabel/GetLabel (顯示文字)、SetHyperLink/GetHyperLink (目標 URL)、Navigate() (啟動導航)。
m_wndLink.SetHyperLink(_T("https://www.codeproject.com/"));

CHyperLink

對話框控件 UI 更新機制

MFC 需回應 WM_KICKIDLE;WTL 簡單得多,但精靈有 Bug 需手動修正。

必要條件:非模態對話框

CUpdateUI 需在程式訊息迴圈控制下運作 (空閒時間處理)。模態對話框由系統迴圈驅動,無空閒處理 → 無 UI 更新。

類別定義關鍵點

class CMainDlg : public CDialogImpl<CMainDlg>,
                 public CUpdateUI<CMainDlg>,
                 public CMessageFilter, public CIdleHandler
{
    virtual BOOL PreTranslateMessage(MSG* pMsg);
    virtual BOOL OnIdle();
    BEGIN_MSG_MAP_EX(CMainDlg)
        MSG_WM_INITDIALOG(OnInitDialog)
        COMMAND_ID_HANDLER_EX(IDOK, OnOK)
        COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
    END_MSG_MAP()
    BEGIN_UPDATE_UI_MAP(CMainDlg)
        UPDATE_ELEMENT(IDC_ALYSON_BMPBTN, UPDUI_CHILDWINDOW)
    END_UPDATE_UI_MAP()
};

OnInitDialog 註冊

CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
UIAddChildWindowContainer(m_hWnd); // 告知包含子視窗容器

修正精靈 Bug:OnIdle 必須呼叫 UIUpdateChildWindows

BOOL CMainDlg::OnIdle()
{
    UIUpdateChildWindows(); // 精靈漏掉這行!
    return FALSE;
}

範例:點擊左側按鈕切換右側按鈕啟用狀態

void CMainDlg::OnAlysonODBtn(UINT, int, HWND)
{
    static bool s_bBtnEnabled = true;
    s_bBtnEnabled = !s_bBtnEnabled;
    UIEnable(IDC_ALYSON_BMPBTN, s_bBtnEnabled);
}

對話框資料驗證 (DDV) - WTL 簡化版

MFC 分離 DDX/DDV 巨集;WTL 整合在單一巨集中:

  • DDX_TEXT_LEN:文字長度驗證
  • DDX_INT_RANGE / DDX_UINT_RANGE:整數範圍驗證
  • DDX_FLOAT_RANGE:浮點數範圍驗證

範例:季節輸入限制 1-7

BEGIN_DDX_MAP(CMainDlg)
    DDX_INT_RANGE(IDC_FAV_SEASON, m_nSeason, 1, 7)
END_DDX_MAP()

DDV 驗證範例

處理驗證失敗:覆寫 OnDataValidateError

void CMainDlg::OnDataValidateError(UINT nCtrlID, BOOL bSave, _XData& data)
{
    CString sMsg;
    sMsg.Format(_T("請輸入 %d 到 %d 之間的數字"), data.intData.nMin, data.intData.nMax);
    MessageBox(sMsg, _T("ControlMania2"), MB_ICONEXCLAMATION);
    ::SetFocus(GetDlgItem(nCtrlID));
}

_XData 結構包含 nDataType (指示聯合體有效成員) 與 intData/floatData/textData。

對話框動態調整大小:CDialogResize

  1. 繼承 CDialogResize
  2. OnInitDialog 呼叫 DlgResize_Init()
  3. 訊息映射鏈結 CHAIN_MSG_MAP(CDialogResize)

詳細用法請參考 Michael Dunn 專文 Using WTL's Built-in Dialog Resizing Class。

參考資源

  • Using WTL's Built-in Dialog Resizing Class - Michael Dunn
  • Using DDX and DDV with WTL - Less Wright

下一章預告

介紹對話框中使用 ActiveX 控件與事件處理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved