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

MFC 程式設計師的 WTL 入門指南 (三):框架視窗基礎類別與訊息映射增強

     編輯:關於VC++

WTL 類別體系概覽

WTL (Windows Template Library) 類別大致分為五大類:

  1. 主框架視窗實作:CFrameWindowImpl, CMDIFrameWindowImpl
  2. 控制項封裝:CButton, CListViewCtrl 等
  3. GDI 物件封裝:CDC, CMenu 等
  4. 特殊介面特性:CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw
  5. 實用工具類別與巨集:CString, CRect, BEGIN_MSG_MAP_EX

本章深入探討框架視窗類別、介面特性類別與工具類別,展示 WTL 如何以輕量級方式實現豐富的 UI 功能。

從零建立 WTL 程式:顯示即時時鐘的主視窗

stdafx.h 必要標頭檔

#define STRICT
#define WIN32_LEAN_AND_MEAN
#define _WTL_USE_CSTRING
#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>
#include <atlframe.h>
#include <atlmisc.h>
#include <atlcrack.h>

關鍵點:_WTL_USE_CSTRING 必須在 atlapp.h 之前定義,確保後續標頭檔能識別 CString。

框架視窗類別定義

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
    DECLARE_FRAME_WND_CLASS(_T("First WTL window"), IDR_MAINFRAME);
    BEGIN_MSG_MAP_EX(CMyWindow)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
    END_MSG_MAP()
};
  • DECLARE_FRAME_WND_CLASS 參數:視窗類別名稱 (可為 NULL 自動產生) 與資源 ID (載入圖示、選單、加速鍵表)。
  • 使用 BEGIN_MSG_MAP_EX 啟用 WTL 增強訊息映射 (包含參數解碼)。
  • CHAIN_MSG_MAP 將未處理訊息傳遞給基類 (CFrameWindowImpl 處理 WM_SIZE, WM_DESTROY 等)。

WinMain 進入點

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
    _Module.Init(NULL, hInstance);
    CMyWindow wndMain;
    if (NULL == wndMain.CreateEx()) return 1;
    wndMain.ShowWindow(nCmdShow);
    wndMain.UpdateWindow();

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    _Module.Term();
    return msg.wParam;
}

WTL 增強訊息映射機制深度解析

ATL 原始訊息映射需手動從 WPARAM/LPARAM 解碼參數,極易出錯。WTL 在 atlcrack.h 提供 MSG_WM_XXX 巨集自動解碼,並透過 SetMsgHandled() 取代 ATL 的 bHandled 參數。

處理 WM_CREATE:建立計時器

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
    BEGIN_MSG_MAP_EX(CMyWindow)
        MSG_WM_CREATE(OnCreate)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
    END_MSG_MAP()

    LRESULT OnCreate(LPCREATESTRUCT lpcs)
    {
        SetTimer(1, 1000); // 1 秒觸發一次
        SetMsgHandled(FALSE); // 讓基類也處理 WM_CREATE
        return 0;
    }
};

巨集 MSG_WM_CREATE 定義揭示處理函式原型:LRESULT OnCreate(LPCREATESTRUCT)。按 F12 可快速跳轉查看巨集定義,極大提升開發效率。

處理 WM_DESTROY:銷毀計時器

BEGIN_MSG_MAP_EX(CMyWindow)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
    CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()

void OnDestroy()
{
    KillTimer(1);
    SetMsgHandled(FALSE);
}

處理 WM_TIMER:觸發重繪

MSG_WM_TIMER(OnTimer)
void OnTimer(UINT uTimerID, TIMERPROC)
{
    if (1 != uTimerID) SetMsgHandled(FALSE);
    else RedrawWindow();
}

處理 WM_ERASEBKGND:繪製時間文字

MSG_WM_ERASEBKGND(OnEraseBkgnd)
LRESULT OnEraseBkgnd(HDC hdc)
{
    CDCHandle dc(hdc);
    CRect rc; GetClientRect(rc);
    SYSTEMTIME st; GetLocalTime(&st);
    CString sTime; sTime.Format(_T("The time is %d:%02d:%02d"), st.wHour, st.wMinute, st.wSecond);

    dc.SaveDC();
    dc.SetBkColor(RGB(255,153,0));
    dc.SetTextColor(RGB(0,0,0));
    dc.ExtTextOut(0, 0, ETO_OPAQUE, rc, sTime, sTime.GetLength(), NULL);
    dc.RestoreDC(-1);
    return 1; // 背景已繪製
}

CDCHandle 為 HDC 輕量封裝,用法類似 MFC CDC 但不會在離開作用域時銷毀裝置內容。

WTL 時鐘程式執行畫面

WTL 應用程式精靈生成的專案架構分析

使用 ATL/WTL AppWizard 建立 SDI 專案 (專案名 WTLClock),取消工具列/重列/狀態列。精靈產生三個核心類別:

  • CMainFrame:主框架視窗
  • CAboutDlg:關於對話方塊 (CDialogImpl 衍生)
  • CWTLClockView:檢視視窗 (CWindowImpl 衍生,無 MFC 式 Document/View 架構)

CMainFrame 關鍵設計

class CMainFrame : public CFrameWindowImpl<CMainFrame>,
                   public CUpdateUI<CMainFrame>,
                   public CMessageFilter,
                   public CIdleHandler
{
public:
    DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
    CWTLClockView m_view;

    virtual BOOL PreTranslateMessage(MSG* pMsg);
    virtual BOOL OnIdle();

    BEGIN_UPDATE_UI_MAP(CMainFrame)
    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP(CMainFrame)
        // ...
        CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
    END_MSG_MAP()
};
  • 多重繼承 CUpdateUI (UI 狀態更新)、CMessageFilter (訊息預處理)、CIdleHandler (閒置處理)。
  • OnCreate 建立檢視視窗並註冊至訊息迴圈:
LRESULT CMainFrame::OnCreate(UINT, WPARAM, LPARAM, BOOL&)
{
    m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL,
        WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
    CMessageLoop* pLoop = _Module.GetMessageLoop();
    pLoop->AddMessageFilter(this);
    pLoop->AddIdleHandler(this);
    return 0;
}

m_hWndClient 為基類成員,指向客戶區視窗,框架大小改變時自動調整該視窗尺寸。

CMessageLoop 內部運作與 UI 更新機制

CMessageLoop::Run() 偽代碼揭示核心流程:

int Run()
{
    MSG msg;
    for(;;)
    {
        while (!PeekMessage(&msg)) DoIdleProcessing();
        if (0 == GetMessage(&msg)) break;
        if (!PreTranslateMessage(&msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return msg.wParam;
}
  • 訊息過濾:PreTranslateMessage 由 CMessageFilter 實作,AddMessageFilter 註冊。
  • 閒置處理:OnIdle 由 CIdleHandler 實作,AddIdleHandler 註冊。
  • UI 更新:CUpdateUI、CIdleHandler、CMessageLoop 三者協同,對應 MFC ON_UPDATE_COMMAND_UI。

CFrameWindowImpl 內部佈局邏輯

LRESULT OnSize(UINT, WPARAM wParam, LPARAM, BOOL& bHandled)
{
    if (wParam != SIZE_MINIMIZED)
    {
        T* pT = static_cast<T*>(this);
        pT->UpdateLayout();
    }
    bHandled = FALSE;
    return 1;
}

void UpdateLayout(BOOL bResizeBars = TRUE)
{
    RECT rect; GetClientRect(&rect);
    UpdateBarsPosition(rect, bResizeBars); // 定位工具列/狀態列
    if (m_hWndClient != NULL)
        ::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top,
            rect.right - rect.left, rect.bottom - rect.top,
            SWP_NOZORDER | SWP_NOACTIVATE);
}

關鍵在於 m_hWndClient 僅為一般 HWND,不限制類型 (不同於 MFC 必須為 CView 衍生類),極大提升彈性。

回歸時鐘程式:加入 UI 更新功能的選單控制

為 CWTLClockView 加入 StartClock/StopClock,主框架新增「時鐘」選單項 IDC_START/IDC_STOP:

BEGIN_UPDATE_UI_MAP(CMainFrame)
    UPDATE_ELEMENT(IDC_START, UPDUI_MENUPOPUP)
    UPDATE_ELEMENT(IDC_STOP, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()

void CMainFrame::OnStart(UINT, int, HWND)
{
    UIEnable(IDC_START, false); UIEnable(IDC_STOP, true);
    m_view.StartClock();
}
void CMainFrame::OnStop(UINT, int, HWND)
{
    UIEnable(IDC_START, true); UIEnable(IDC_STOP, false);
    m_view.StopClock();
}

啟動時初始化選單狀態:

UIEnable(IDC_START, false); UIEnable(IDC_STOP, true);

ClassView 相容性修正

VC6 ClassView 無法解析 BEGIN_MSG_MAP_EX,可在 stdafx.h 末尾加入:

#undef BEGIN_MSG_MAP
#define BEGIN_MSG_MAP(x) BEGIN_MSG_MAP_EX(x)

下一章預告

將介紹工具列、狀態列、重列等 Windows 95 標準介面元素,並實作 UISetCheck 等進階 UI 更新功能。

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