Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1569235
  • 博文数量: 884
  • 博客积分: 52280
  • 博客等级: 大将
  • 技术积分: 13060
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-06 09:46
文章分类

全部博文(884)

文章存档

2008年(884)

我的朋友

分类: C/C++

2008-08-06 10:00:14

下载本文示例代码
ATL有一个专门为窗口设计的接口,可以做全部的窗口操作,这就是CWindow。它实际上就是对HWND操作的包装类,对几乎所有以HWND句柄为第一个参数的窗口API的进行了封装,例如:SetWindowText() 和 DestroyWindow()。CWindow类有一个公有成员m_hWnd,使你可以直接对窗口的句柄操作,CWindow还有一个操作符HWND,你可以讲CWindow对象传递给以HWND为参数的函数,但这与CWnd::GetSafeHwnd()(译者加:MFC的方法)没有任何等同之处。

CWindow 与 MFC 的CWnd类有很大的不同,创建一个CWindow对象占用很少的资源,因为只有一个数据成员,没有MFC窗口中的对象链,MFC内部维持这一个对象链,此对象链将HWND映射到CWnd对象。还有一点与MFC的CWnd类不同的是当一个CWindow对象超出了作用域,它关联的窗口并不被销毁掉,这意味着你并不需要随时记得分离你所创建的临时CWindow对象。

在ATL类中对窗口过程的实现是CWindowImpl。CWindowImpl 含有所有窗口实现代码,例如:窗口类的注册,窗口的子类化,消息映射以及基本的WindowProc()函数,可以看出这与MFC的设计有很大的不同,MFC将所有的代码都放在一个CWnd类中。

还有两个独立的类包含对话框的实现,它们分别是CDialogImpl 和 CAxDialogImpl,CDialogImpl 用于实现普通的对话框而CAxDialogImpl实现含有ActiveX控件的对话框。

定义一个窗口的实现

任何非对话框窗口都是从CWindowImpl 派生的,你的新类需要包含三件事情:

  1. 一个窗口类的定义
  2. 一个消息映射链
  3. 窗口使用的默认窗口类型,称为window traits

窗口类的定义通过DECLARE_WND_CLASS宏或DECLARE_WND_CLASS_EX宏来实现。这辆个宏定义了一个CWndClassInfo结构,这个结构封装了WNDCLASSEX结构。DECLARE_WND_CLASS宏让你指定窗口类的类名,其他参数使用默认设置,而DECLARE_WND_CLASS_EX宏还允许你指定窗口类的类型和窗口的背景颜色,你也可以用NULL作为类名,ATL会自动为你生成一个类名。

让我们开始定义一个新类,在后面的章节我会逐步的完成这个类的定义。

class CMyWindow : public CWindowImpl

{

public:

    DECLARE_WND_CLASS(_T("My Window Class"))

};

接下来是消息映射链,ATL的消息映射链比MFC的简单的多,ATL的消息映射链被展开为switch语句,switch语句正确的消息处理者并调用相应的函数。使用消息映射链的宏是BEGIN_MSG_MAP 和 END_MSG_MAP,让我们为我们的窗口添加一个空的消息映射链。

class CMyWindow : public CWindowImpl

{

public:

    DECLARE_WND_CLASS(_T("My Window Class"))

 

    BEGIN_MSG_MAP(CMyWindow)

    END_MSG_MAP()

};

我将在下一节展开讲如何如何添加消息处理到消息映射链。最后,我们需要为我们的窗口类定义窗口的特征,窗口的特征就是窗口类型和扩展窗口类型的联合体,用于创建窗口时指定窗口的类型。窗口类型被指定为参数模板,所以窗口的调用者不需要为指定窗口的正确类型而烦心,下面是是同ATL类CWinTraits定义窗口类型的例子:

typedef CWinTraits CMyWindowTraits;

 

class CMyWindow : public CWindowImpl

{

public:

    DECLARE_WND_CLASS(_T("My Window Class"))

 

    BEGIN_MSG_MAP(CMyWindow)

    END_MSG_MAP()

};

调用者可以重载CMyWindowTraits的类型定义,但是一般情况下这是没有必要的,ATL提供了几个预先定义的特殊的类型,其中之一就是CFrameWinTraits,一个非常棒的框架窗口:

typedef CWinTraits                                         WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>  CFrameWinTraits;
填写消息映射链

ATL的消息映射链是对开发者不太友好的部分,也是WTL对其改进最大的部分。类向导至少可以让你添加消息响应,然而ATL没有消息相关的宏和象MFC那样的参数自动展开功能,在ATL中只有三种类型的消息处理,一个是WM_NOTIFY,一个是WM_COMMAND,第三类是其他窗口消息,让我们开始为我们的窗口添加WM_CLOSE 和 WM_DESTROY的消息相应函数。

class CMyWindow : public CWindowImpl

{

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值,你需要自己将其展开为相应的消息所需要的参数。还有第四个参数bHandled,这个参数在消息相应函数调用被ATL设置为TRUE,如果在你的消息响应处理完之后需要ATL调用默认的WindowProc()处理该消息,你可以讲bHandled设置为FALSE。这与MFC不同,MFC是显示的调用基类的响应函数来实现的默认的消息处理的。

让我们也添加一个对WM_COMMAND消息的处理,假设我们的窗口有一个ID为IDC_ABOUT的About菜单:

class CMyWindow : public CWindowImpl

{

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宏已经将消息的参数展开了,同样,NOTIFY_HANDLER宏也将WM_NOTIFY消息的参数展开了。

高级消息映射链和嵌入类

ATL的另一个显著不同之处就是任何一个C 类都可以响应消息,而MFC只是将消息响应任务分给了CWnd类和CCmdTarget类,外加几个有PreTranslateMessage()方法的类。ATL的这种特性允许我们编写所谓的“嵌入类”,为我们的窗口添加特性只需将该类添加到继承列表中就行了,就这么简单!

一个基本的带有消息映射链的类通常是模板类,将派生类的类名作为模板的参数,这样它就可以访问派生类中的成员,比如m_hWnd(CWindow类中的HWND成员)。让我们来看一个嵌入类的例子,这个嵌入类通过响应WM_ERASEBKGND消息来画窗口的背景。

template 

class CPaintBkgnd : public CMessageMap

{

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(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有两个模板参数:使用CPaintBkgnd的派生类的名字和用来画窗口背景的颜色。(t_ 前缀通常用来作为模板类的模板参数的前缀)CPaintBkgnd也是从CMessageMap派生的,这并不是必须的,因为所有需要响应消息的类只需使用BEGIN_MSG_MAP宏就足够了,所以你可能看到其他的一些嵌入类的例子代码,它们并不是从该基类派生的。

构造函数和析构函数都相当简单,只是创建和销毁Windows画刷,这个画刷由参数t_crBrushColor决定颜色。接着是消息映射链,它响应WM_ERASEBKGND消息,最后由响应函数OnEraseBkgnd()用构造函数创建的画刷填充窗口的背景。在OnEraseBkgnd()中有两件事需要注意,一个是它使用了一个派生的窗口类的方法(即GetClientRect()),我们如何知道派生类中有GetClientRect()方法呢?如果派生类中没有这个方法我们的代码也不会有任何抱怨,由编译器确认派生类T是从CWindow派生的。另一个是OnEraseBkgnd()没有将消息参数wParam展开为设备上下文(DC)。(WTL最终会解决这个问题,我们很快就可以看到,我保证)

要在我们的窗口中使用这个嵌入类需要做两件事:首先,将它加入到继承列表:

class CMyWindow : public CWindowImpl,

                  public CPaintBkgnd

其次,需要CMyWindow将消息传递给CPaintBkgnd,就是将其链入到消息映射链,在CMyWindow的消息映射链中加入CHAIN_MSG_MAP宏:

class CMyWindow : public CWindowImpl,

                  public CPaintBkgnd 

{

...

typedef CPaintBkgnd 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()

...

};

任何CMyWindow没有处理的消息都被传递给CPaintBkgnd。应该注意的是WM_CLOSE,WM_DESTROY和IDC_ABOUT消息将不会传递,因为这些消息一旦被处理消息映射链的查找就会中止。使用typedef是必要地,因为宏是预处理宏,只能有一个参数,如果我们将CPaintBkgnd作为参数传递,那个“,”会使预处理器认为我们使用了多个参数。

你可以在继承列表中使用多个嵌入类,每一个嵌入类使用一个CHAIN_MSG_MAP宏,这样消息映射链就会将消息传递给它。这与MFC不同,MFC地CWnd派生类只能有一个基类,MFC自动将消息传递给基类。

ATL程序的结构

到目前为止我们已经有了一个完整地主窗口类(即使不完全有用),让我们看看如何在程序中使用它。一个ATL程序包含一个CComModule类型的全局变量_Module,这和MFC的程序都有一个CWinApp类型的全局变量theApp有些类似,唯一不同的是在ATL中这个变量必须命名为_Module。

下面是stdafx.h文件的开始部分:

// stdafx.h:

#define STRICT

#define VC_EXTRALEAN

 

#include         // 基本的ATL类

extern CComModule _Module;  // 全局_Module

#include          // ATL窗口类

atlbase.h已经包含最基本的Window编程的头文件,所以我们不需要在包含windows.h,tchar.h之类的头文件。在CPP文件中声明了_Module变量:

// main.cpp:

CComModule _Module;

CComModule含有程序的初始化和关闭函数,需要在WinMain()中显示的调用,让我们从这里开始:

// main.cpp:

CComModule _Module;

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,

                   LPSTR szCmdLine, int nCmdShow)

{

    _Module.Init(NULL, hInst);

    _Module.Term();

}

Init()的第一个参数只有COM的服务程序才有用,由于我们的EXE不含有COM对象,我们只需将NULL传递给Init()就行了。ATL不提供自己的WinMain()和类似MFC的消息泵,所以我们需要创建CMyWindow对象并添加消息泵才能使我们的程序运行。

// main.cpp:

#include "MyWindow.h"

CComModule _Module;

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,

                   LPSTR szCmdLine, int nCmdShow)

{

    _Module.Init(NULL, hInst);

 

    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);

    }

 

    _Module.Term();

    return msg.wParam;

}

上面的代码唯一需要说明的是CWindow::rcDefault,这是CWindow中的成员(静态数据成员),数据类型是RECT。和调用CreateWindow() API时使用CW_USEDEFAULT指定窗口的宽度和高度一样,ATL使用rcDefault作为窗口的最初大小。

在ATL代码内部,ATL使用了一些类似汇编语言的魔法将主窗口的句柄与相应的CMyWindow对象联系起来,在外部看来就是可以毫无问题的在线程之间传递CWindow对象,而MFC的CWnd却不能这样作。

这就是我们的窗口:

 [First ATL window - 4K]

我得承认这确实没有什么激动人心的地方。我们将添加一个About菜单并显示一个对话框,主要是为它增加一些情趣。

ATL中的对话框

我们前面提到过,ATL有两个对话框类,我们的About对话框使用CDialogImpl。生成一个新对话框和生成一个主窗口几乎一样,只有两点不同:

  1. 窗口的基类是CDialogImpl而不是CWindowImpl。
  2. 你需要定义名称为IDD的公有成员用来保存对话框资源的ID。

现在开始为About对话框定义一个新类:

class CAboutDlg : public CDialogImpl

{

public:

    enum { IDD = IDD_ABOUT };

 

    BEGIN_MSG_MAP(CAboutDlg)

    END_MSG_MAP()

};

ATL没有在内部实现对“OK”和“Cancel”两个按钮的响应处理,所以我们需要自己添加这些代码,如果用户用鼠标点击标题栏的关闭按钮,WM_CLOSE的响应函数就会被调用。我们还需要处理WM_INITDIALOG消息,这样我们就能够在对话框出现时正确的设置键盘焦点,下面是完整的类定义和消息响应函数。

class CAboutDlg : public CDialogImpl

{

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;

    }

};

我使用一个消息响应函数同时处理“OK”和“Cancel”两个按钮的WM_COMMAND消息,因为命令响应函数的wID参数就已经指明了消息是来自“OK”按钮还是来自“Cancel”按钮。

显示对话框的方法与MFC相似,创建一个新对话框类的实例,然后调用DoModal()方法。现在我们返回主窗口,添加一个带有About菜单项的菜单用来显示我们的对话框,这需要再添加两个消息响应函数,一个是响应WM_CREATE,另一个是响应菜单的IDC_ABOUT命令。

class CMyWindow : public CWindowImpl,

                  public CPaintBkgnd

{

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(),

                             MAKEINTRESOURCE(IDR_MENU1) );

 

        SetMenu ( hmenu );

        return 0;

    }

 

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

    {

    CAboutDlg dlg;

 

        dlg.DoModal();

        return 0;

    }

    // ...

};

在指定对话框的父窗口的方式上有些不同,MFC是通过构造函数将父窗口的指针传递给对话框而在ATL中是将父窗口的指针作为DoModal()方法的第一个参数传递给对话框的,如果象上面的代码一样没有指定父窗口,ATL会使用GetActiveWindow()得到的窗口(也就是我们的主框架窗口)作为对话框的父窗口。

对LoadMenu()方法的调用展示了CComModule的另一个方法-GetResourceInstance(),它返回你的EXE的HINSTANCE实例,和MFC的AfxGetResourceHandle()方法相似。(当然还有CComModule::GetModuleInstance(),它相当于MFC的AfxGetInstanceHandle()。)

这就是主窗口和对话框的显示效果:

 [About box - 5K]

我会继续讲WTL,我保证!

我会继续讲WTL的,只是会在第二部分。我觉得既然这些文章是写给使用MFC的开发者的,所以有必要在投入WTL之前先介绍一些ATL。如果你是第一次接触到ATL,那现在你就可以尝试写一些小程序,处理消息和使用嵌入类,你也可以尝试用类向导支持消息映射链,使它能够自动添加消息响应。现在就开始,右键单击CMyWindow项,在弹出的上下文菜单中单击“Add Windows Message Handler”菜单项。

在第二部分,我将全面介绍基本的WTL窗口类和一个更好的消息映射宏。

修改记录

2003年3月22日,本文第一次发表。

下载本文示例代码
阅读(233) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~