WTL for MFC Programmers, Part I - ATL GUI Classes

WTL for MFC Programmers

本文章總結自 這篇文章

本章內容

  • ATL 背景知識
  • ATL 窗口類
  • ATL 窗口實現(xiàn)
  • ATL 對話框實現(xiàn)

ATL 背景知識

WTL 是構建于 ATL 之上的一系列附加類。要學習 WTL 首先得對 ATL 進行一些介紹。

ATL 和 WTL 的發(fā)展歷史

Active Template Library(活動模板庫), 是為了方便進行 COM 組件和 ActiveX 控件開發(fā)而誕生的。由于 ATL 是為了開發(fā) COM 而存在的,所以只提供了非常簡單的界面類。直接用 ATL 開發(fā)界面程序是比較繁瑣的。所以才會在此之上封裝 WTL 來方便開發(fā)界面程序。

ATL 風格的模版

class CMyWnd : public CWindowImpl<CMyWnd>
{
    // do something ...
};

上面的代碼初看可能覺得很奇怪,為啥 CMyWnd 繼承了 CWindowImpl, CWindowImpl 又拿 CMyWnd 當模版?這么做不會報錯嗎?這么做有什么作用?

首先,這樣做不會報錯,因為 C++ 的語法解釋說即使 CMyWnd 類只是被部分定義,類名 CMyWnd 已經被列入遞歸繼承列表,是可以使用的。

下面的例子解釋了這種寫法如何工作:

template <class T>
class B1
{
public:
    void SayHi()
    {
        T* pT = static_cast<T*>(this);
        pT->PrintClassName();
    }
    void PrintClassName() { printf("This is B1\n"); }
};

class D1 : public B1<D1>
{
    // 沒有覆寫任何函數(shù)
};

class D2 : public B1<D2>
{
public:
    void PrintClassName() { printf("This is D2\n"); }
};

int main()
{
    D1 d1;
    D2 d2;

    d1.SayHi();    // This is B1
    d2.SayHi();    // This is D2

    return 0;
}

上述代碼實現(xiàn)了類似于“虛函數(shù)”的多態(tài)功能。

通過這種模版寫法, D2 繼承的 B1.SayHi 函數(shù),實際上被解釋成:

void B1<D2>::SayHi()
{
    D2* pT = static_cast<D2*>(this);
    pT->PrintClassName();
}

SayHi 調用的是 D2 的 PrintClassName 方法。

如果不使用這種模版寫法,那么 B1 的 SayHi 函數(shù)在調用 PrintClassName 的時候,只能去調用 B1 自己的 PrintClassName 函數(shù),無法做到調用 D2 覆寫后的 PrintClassName 函數(shù)。

這樣做的好處如下:

  1. 不需要使用指向對象的指針,可以直接使用對象來調用多態(tài)接口;
  2. 節(jié)省內存,因為不需要虛函數(shù)表;
  3. 因為沒有虛函數(shù)表所以不會發(fā)生在運行時調用空指針指向的虛函數(shù);
  4. 所有的函數(shù)在編譯時確定(區(qū)別于 C++ 的虛函數(shù)機制,在運行時確定調用哪個函數(shù))。有利于編譯程序對代碼的優(yōu)化;

回到最初的代碼:

class CMyWnd : public CWindowImpl<CMyWnd>
{
    // do something ...
};

這種寫法的作用也就可以理解了。 CMyWnd 中覆寫的函數(shù),將能夠以類似多態(tài)的方式被 CWindowImpl 正確調用。并且節(jié)省了虛函數(shù)表帶來的內存開銷。

ATL 窗口類

CWindow:

封裝了所有對 HWND 的操作,幾乎所有以 HWND 為第一個參數(shù)的窗口 API 都經過了 CWindow 的封裝。 CWindow 類有一個公有成員 m_hWnd 使你可以直接對窗口進行操作。

CWindowImpl:

繼承自 CWindow, 使用它可以對窗口消息進行處理,從而使窗口具有不同通過的功能和表現(xiàn)。另外它還封裝了 窗口類的注冊,窗口的子類化 等功能。

CAxWindow:

繼承自 CWindow, 用于實現(xiàn)含有 ActiveX 控件的窗口;

CDialogImpl:

繼承自 CWindow, 用于實現(xiàn)普通的對話框;

CAxDialogImpl:

繼承自 CWindow, 用于實現(xiàn)含有 ActiveX 控件的對話框;

ATL 窗口實現(xiàn):

要實現(xiàn)一個 ATL 窗口,要按照如下的步驟:

  1. 在 stdafx.h 中添加 ATL 相關的頭文件:

    #include <atlbase.h>       // 基本 ATL 類
    extern CComModule _Module; // 全局 _Module
    #include <atlwin.h>        // 窗口 ATL 類
    
  2. 在 main.cpp 中定義 CComModule _Module 并初始化它:

    #include "stdafx.h"
    
    CComModule _Module;
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                         _In_opt_ HINSTANCE hPrevInstance,
                         _In_ LPWSTR    lpCmdLine,
                         _In_ int       nCmdShow)
    {
        _Module.Init(NULL, hInstance); // 初始化 _Module
    
        // 在這里進行 ATL 窗口的創(chuàng)建、消息泵的創(chuàng)建 ...
    
        _Module.Term();                // 結束 _Module
        return 0;
    }
    

    一個 ATL 程序包含一個 CComModule 類型的全局變量 _Module, 這和 MFC 程序都有一個 CWinApp 類型的全局變量 theApp 有點兒類似,唯一不同的是在 ATL 中這個變量必須被命名為 _Module.
    _Module 在 main.cpp 中定義并初始化,并通過 extern 關鍵字在 stdafx.h 文件中聲明,其他 #include "stdafx.h" 的模塊就可以使用 _Module 來進行一些操作。

  3. 在 MyWindow.h 中定義自己的窗口 CMyWindow:

    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
    {
    public:
        DECLARE_WND_CLASS(_T("My Window Class"))   // 指定窗口類名
    
        // 消息映射表
        BEGIN_MSG_MAP(CMyWindow)
            MESSAGE_HANDLER(WM_CLOSE, OnClose)     // 在這里將消息映射到函數(shù)
            MESSAGE_HANDLER(WM_DESTROY, OnDestroy) //
        END_MSG_MAP()
    
        LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
        {
            DestroyWindow();
            return 0;
        }
    
        LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
        {
            PostQuitMessage(0);
            return 0;
        }
    };
    

    注意第一行代碼 class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
    模板參數(shù)中的第一個,這樣寫的原因之前已經解釋過,是為了實現(xiàn)類似“多態(tài)”的效果;
    模板參數(shù)中的第二個,目前不知道原因;
    模板參數(shù)中的第三個,用于指定窗口類型,如 WS_OVERLAPPEDWINDOW, WS_EX_APPWINDOW 等, CFrameWinTraits 是 ATL 預先定義的特殊類型,你也可以自己定義:

    typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW> CMyWindowTraits;
    class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
    
  4. 在 main.cpp 中使用 CMyWindow 類創(chuàng)建主窗口:

    #include "stdafx.h"
    #include "MyWindow.h"
    
    CComModule _Module;
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                         _In_opt_ HINSTANCE hPrevInstance,
                         _In_ LPWSTR    lpCmdLine,
                         _In_ int       nCmdShow)
    {
        _Module.Init(NULL, hInstance);
    
        // 聲明 CMyWindow 對象
        CMyWindow wndMain;
    
        // 創(chuàng)建窗口
        if (NULL == wndMain.Create(NULL, CWindow::rcDefault, _T("My First ATL Window")))
        {
            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;
    }
    

ATL 對話框實現(xiàn):

要實現(xiàn)一個 ATL 對話框,和生成 ATL 窗口的方式差不多,只有兩點不同:

  1. 窗口的基類是 CDialogImpl 而不是 CWindowImpl;

  2. 你需要在對話框類中定義名稱為 IDD 的公有成員用來保存對話框資源的 ID;

    #include "resource.h"
    
    class CAboutDlg : public CDialogImpl<CAboutDlg>
    {
    public:
        enum { IDD = IDD_ABOUT };
    
        BEGIN_MSG_MAP(CAboutDlg)
            MESSAGE_HANDLER(WM_INITDIALOG, OnInitControl)
            MESSAGE_HANDLER(WM_CLOSE, OnClose)
            COMMAND_ID_HANDLER(IDOK, OnOkCancel)
            COMMAND_ID_HANDLER(IDCANCEL, OnOkCancel)
        END_MSG_MAP()
    
        LRESULT OnInitControl(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
        {
            CenterWindow();
            return TRUE;
        }
    
        LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
        {
            EndDialog(0);
            return 0;
        }
    
        LRESULT OnOkCancel(WORD wNotifyCode, WORD wID, HWND hWndCtrl, BOOL& bHandled)
        {
            EndDialog(wID);
            return 0;
        }
    };
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

  • Windows 95中文輸入法編輯器(IME) 微軟 翻譯:TBsoft Software Studio ...
    returntrue閱讀 2,703評論 0 3
  • 實驗一 unresolved external symbol __endthreadex錯誤解決,是因為沒有引用M...
    yueyue_projects閱讀 2,718評論 0 5
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,722評論 25 708
  • 對,我愛他,但是那晚我也拒絕了他。 這個故事我講了三遍,內容不同,互相補充,唯一相同的是隱藏了我愛他的事實。 1....
    heikf閱讀 418評論 0 0
  • 認識自己的能力,清楚自己的優(yōu)缺點,并以此來選擇專業(yè),是一種辦法。 結合自己的興趣愛好和能力優(yōu)勢 序號 學科興趣類型...
    燕妮老師說閱讀 444評論 0 0