WTL入门(1)-- ATL背景知识

本文适用于对MFC比较了解的中级开发人员。

源代码下载:http://download.csdn.net/source/3522785

ATL Background

WTL入门(1)-- ATL背景知识_第1张图片

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;

    }

    // ...

};
参考原文: http://www.codeproject.com/KB/wtl/wtl4mfc1.aspx

你可能感兴趣的:(command,null,Class,mfc,编译器,winapi)