WTL入門(1)-- ATL背景知識

方寸之間發表於2011-07-28

本文適用於對MFC比較瞭解的中級開發人員。

原始碼下載:http://download.csdn.net/source/3522785

ATL Background

ATL-style templates

 

class CMyWnd : public CWindowImpl<CMyWnd>

{

...

};


這是合法的。因為C++規範上說在class CMyWnd之後CmyWnd立即被定義並且可以用於繼承列表。ATL中用這種方法可以實現編譯時虛擬函式呼叫。

看下面例子:

template <class T>

class B1

{

public: 

    void SayHi() 

{

    // 此處是關鍵技巧。

        T* pT = static_cast<T*>(this);   

 

        pT->PrintClassName();

    }

    void PrintClassName() { cout << "This is B1"; }

};

 

class D1 : public B1<D1>

{

    // No overridden functions at all

};

 

class D2 : public B1<D2>

{

    void PrintClassName() { cout << "This is D2"; }

};

 

int main()

{

D1 d1;

D2 d2;

 

d1.SayHi();    // prints "This is B1"

d2.SayHi();    // prints "This is D2"

 

return 0;

}

static_cast<T*>(this)是關鍵技術。它將B1*this根據不同的呼叫轉化為D1*D2*。由於模板程式碼是在編譯時生成的,只要繼承列表編寫正確,這種型別轉換就是安全的。(避免寫出class D3 : public B1<D2>這種形式的程式碼。編譯器是檢測不出的。)

第一次呼叫SayHi()時,程式碼實際是這樣的:

void B1<D1>::SayHi()

{

D1* pT = static_cast<D1*>(this);

    pT->PrintClassName();

}


D1沒有重寫PrintClassName(),因此去搜尋D1的基類。B1有PrintClassName(),因此實際呼叫B1::PrintClassName()。
void B1<D2>::SayHi()

{

D2* pT = static_cast<D2*>(this);

    pT->PrintClassName();

}

D2重寫了函式PrintClassName(),直接呼叫此函式。

這種技術的優點:

1) 不需要使用物件的指標

2) 節省記憶體,因為不需要使用虛擬函式表

3) 不會因為未初始化的虛擬函式表導致使用NULL指標

4) 所有函式的呼叫在編譯時確定,因此它們是可以優化的。

ATL Windowing Classes

ATL嚴格遵守介面-實現分離的原則。

ATL擁有一個定義Window的介面類:CWindow。它僅僅封裝了HWND,並且封裝了幾乎所有的Use32 APIs中以HWND為第一個引數的介面。例如SetWindowText()DestroyWindow()CWindow提供一個共有的成員m_hWnd,可以直接處理HWND,也提供了一個operator HWND(),可以直接是CWindow作為需要HWND物件的函式引數。

CWindow不同於MFC中的 CWndCWindow易於建立的,它不提供像MFC中的HWNDCWnd的物件關係。當CWindow物件超出作用域時,它被銷燬,但是它關聯的實際視窗不會被銷燬。因此不需要detach你建立的臨時的CWindow物件。

ATL還提供了一個Window的實現類CWindowImpl。它包含了下列處理:視窗註冊、視窗子類、訊息對映、以及一個基本的WindowProc().不想MFC中的CWnd,所有的東西都在這個類中。

關於對話方塊的實現,ATL提供了兩個獨立的實現類CDialogImpl 和 CAxDialogImpl。前者用於普通對話方塊 ,後者用於ActiveX控制元件。

Defining a Window Implementation

定義一個非對話方塊的視窗類,要從CWindowImpl派生。新類中必須包含三件事情:

1) 視窗類定義

2) 訊息對映

3) 一個預設的窗體特徵,叫做window traits

1、 視窗類定義用巨集DECLARE_WND_CLASSDECLARE_WND_CLASS_EX.二者均定義了一個ATL結構CWndClassInfo,它封裝了WNDCLASSEX結構。前者僅僅定義新的視窗類的名字,其他引數用預設值;後者還可以定義視窗風格和背景色。視窗類的名字可以是NULL,此時ATL會自動生成一個。

class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))
};

2、 訊息對映,與MFC類似。ATL 訊息對映將之擴充套件為一個Switch語句,選擇正確的控制程式碼執行對應的函式。
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))
    BEGIN_MSG_MAP(CMyWindow)
    END_MSG_MAP()
};

3、 定義window traits. 它是一個窗體風格和用於建立窗體的風格的組合。它們將作為模板引數來封裝窗體特徵類,這樣呼叫者不必困擾於當建立視窗時如何獲取正確的窗體風格。
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,

                   WS_EX_APPWINDOW> CMyWindowTraits;

 

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>

{

public:

    DECLARE_WND_CLASS(_T("My Window Class"))

 

    BEGIN_MSG_MAP(CMyWindow)

    END_MSG_MAP()

};


呼叫者可以重寫CMyWindowTraits的定義,但是這通常是不需要的。ATL也提供了一些預定義的窗體特徵類:如CFrameWinTraits

 

typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |

                     WS_CLIPSIBLINGS,

                   WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>

        CFrameWinTraits;


Filling in the message map

ATL不提供像MFC那樣的自動生成訊息對映的巨集。但是僅僅有三種型別的訊息處理:一種像WM_NOTIFY一種像WM_COMMAND,另一種就是其他型別的訊息處理。

第三類訊息:

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>

{

public:

    DECLARE_WND_CLASS(_T("My Window Class"))

 

    BEGIN_MSG_MAP(CMyWindow)

        MESSAGE_HANDLER(WM_CLOSE, OnClose)

        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;

    }

};

訊息處理函式封裝了WPARAM 和 LPARAM,在使用時需要對它們進行解碼。對於第四個引數,ATL在訊息處理之前會把它設為TRUE,如果你想讓ATL的預設WindowProc()本訊息處理函式結束之後也處理此訊息,需要設定此引數為FALSE。這和MFC不同,你必須明確呼叫基類的訊息處理函式。

第二類訊息:WM_COMMAND

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>

{

public:

    DECLARE_WND_CLASS(_T("My Window Class"))

 

    BEGIN_MSG_MAP(CMyWindow)

        MESSAGE_HANDLER(WM_CLOSE, OnClose)

        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)

        COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)

    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;

    }

 

    LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)

    {

        MessageBox ( _T("Sample ATL window"), _T("About MyWindow") );

        return 0;

    }

};

COMMAND_HANDLER巨集把WPARAM 和 LPARAM已經解碼了。

同樣第一類訊息,通過NOTIFY_HANDLER訊息引數解碼。

Advanced Message Maps and Mix-in Classes

ATL中的任一C++類均可處理訊息對映。這可以讓我們編寫混合類,我們通過新增此類到繼承列表中來為窗體新增新特性。

一個帶訊息對映的基類通常是一個把派生類名字作為模板引數的模板類。因此它可以處理派生類的成員,如m_hWnd(the HWND member inCWindow)。

下面一個例子是處理WM_ERASEBKGND的一個混合類。

template <class T, COLORREF t_crBrushColor>
class CPaintBkgnd
{
public:
    CPaintBkgnd() { m_hbrBkgnd = CreateSolidBrush(t_crBrushColor); }
    ~CPaintBkgnd() { DeleteObject ( m_hbrBkgnd ); }
    BEGIN_MSG_MAP(CPaintBkgnd)
        MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
    END_MSG_MAP()
    LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
    T*   pT = static_cast<T*>(this);
    HDC  dc = (HDC) wParam;
    RECT rcClient;
        pT->GetClientRect ( &rcClient );
        FillRect ( dc, &rcClient, m_hbrBkgnd );
        return 1;    // we painted the background
    }
protected:
    HBRUSH m_hbrBkgnd;
};

首先CPaintBkgnd類有兩個模板引數,一個是將要用此類的派生類名字,另一個是背景色。(t_字首通常用於值型別模板引數)。建構函式和解構函式用於建立和銷燬畫刷。訊息對映處理WM_ERASEBKGND。最後,OnEraseBkgnd()實現用建構函式中建立的畫刷填充視窗。1)它呼叫了其派生視窗類中的函式GetClientRect(),

如果派生類沒有此函式,編譯器將會提示錯誤。2)OnEraseBkgnd()需要從wParam解碼出裝置上下文。

要使用此混合類,首先把它新增到繼承列表,然後用CHAIN_MSG_MAP巨集傳遞訊息到此類中。

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
                  public CPaintBkgnd<CMyWindow, RGB(0,0,255)> 
{
...
typedef CPaintBkgnd<CMyWindow, RGB(0,0,255)> CPaintBkgndBase;
    BEGIN_MSG_MAP(CMyWindow)
        MESSAGE_HANDLER(WM_CLOSE, OnClose)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        COMMAND_HANDLER(IDC_ABOUT, OnAbout)
        CHAIN_MSG_MAP(CPaintBkgndBase)
    END_MSG_MAP()
...
};

任何到達CHAIN_MSG_MAP行還沒有被處理的訊息都會被傳入到CPaintBkgnd中。WM_CLOSE、WM_DESTROY和IDC_ABOUT將不會被連結到CPaintBkgnd,因為一旦訊息被處理,訊息路由的搜尋就結束了。typedef語句是必須的,因為CHAIN_MSG_MAP是一個只接受一個引數的預處理巨集,如果不這樣寫的話,前處理器將會認為它不只是一個引數。

一個視窗類可以有多個混合類,每一個有一個CHAIN_MSG_MAP巨集。

Structure of An ATL exe

以VC8為例:VC7以來,ATL標頭檔案自動宣告全域性的CComModule例項,以及初始化Init()和終止函式Term()。因此這些步驟就不需要了。

// stdafx.h:

#define STRICT

#define WIN32_LEAN_AND_MEAN

 

#include <atlbase.h>        // Base ATL classes

#include <atlwin.h>         // ATL windowing classes

WinMain()函式不需要呼叫任何_Module函式
// main.cpp:

#include "MyWindow.h"

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,

                   LPSTR szCmdLine, int nCmdShow)

{

CMyWindow wndMain;

MSG msg;

 

    // Create & show our main window

    if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault, 

                                 _T("My First ATL Window") ))

        {

        // Bad news, window creation failed

        return 1;

        }

 

    wndMain.ShowWindow(nCmdShow);

    wndMain.UpdateWindow();

 

    // Run the message loop

    while ( GetMessage(&msg, NULL, 0, 0) > 0 )

        {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

        }

 

    return msg.wParam;

}

C++於處理器:WIN32;_WINDOWS。LINK子系統選擇Windows。

Dialogs in ATL

前面講過ATL有兩種Dialog。此處用CDialogImpl。建立一個新的對話方塊和建立一個新的框架視窗很相似。有兩點不同:

1)   用CDialogImpl替換CWindowImpl

2)   定義IDD指向對話方塊資源的ID

class CAboutDlg : public CDialogImpl<CAboutDlg>

{

public:

    enum { IDD = IDD_ABOUT };

 

    BEGIN_MSG_MAP(CAboutDlg)

    END_MSG_MAP()

};


ATL沒有定義OK和CANCEL兩個button的處理,需要自己定義。處理WM_INITDIALOG訊息,初始化對話方塊的表現。

class CAboutDlg : public CDialogImpl<CAboutDlg>

{

public:

    enum { IDD = IDD_ABOUT };

 

    BEGIN_MSG_MAP(CAboutDlg)

        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)

        MESSAGE_HANDLER(WM_CLOSE, OnClose)

        COMMAND_ID_HANDLER(IDOK, OnOKCancel)

        COMMAND_ID_HANDLER(IDCANCEL, OnOKCancel)

    END_MSG_MAP()

 

    LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

    {

        CenterWindow();

        return TRUE;    // let the system set the focus

    }

 

    LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

    {

        EndDialog(IDCANCEL);

        return 0;

    }

 

    LRESULT OnOKCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)

    {

        EndDialog(wID);

        return 0;

    }

};

處理訊息window的WM_CREATE訊息,載入menu,響應選單ABOUT訊息彈出ABOUT對話方塊
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,

                  public CPaintBkgnd<CMyWindow,RGB(0,0,255)>

{

public:

    BEGIN_MSG_MAP(CMyWindow)

        MESSAGE_HANDLER(WM_CREATE, OnCreate)

        COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)

        // ...

        CHAIN_MSG_MAP(CPaintBkgndBase)

    END_MSG_MAP()

 

    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

    {

    HMENU hmenu = LoadMenu ( _Module.GetResourceInstance(),  // _AtlBaseModule in VC7

                             MAKEINTRESOURCE(IDR_MENU1) );

        SetMenu ( hmenu );

        return 0;

    }

 

    LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)

    {

        CAboutDlg dlg;

        dlg.DoModal();

        return 0;

    }

    // ...

};

相關文章