WTL for MFC Programmers, Part I - ATL GUI Classes

Contents

README.TXT

This is the stuff I want you to read first, before proceeding on or posting messages to this article's discussion board.

在你在这篇文章的讨论板上处理或回答留言时,我希望你先读一下这篇文章.

This series of articles originally covered WTL 7.0 and written for VC 6 users. Now that VC 8 is out, I felt it was about time to update the articles to cover VC 7.1. ;) (Also, the automatic 6-to-7 conversion done by VC 7.1 doesn't always go smoothly so VC 7.1 users could get stuck when trying to use the demo source code.) So as I go through and update this series, the articles will be updated to reflect new WTL 7.1 features, and I'll have VC 7.1 projects in the source downloads.

这一系列文章基本上含盖了WTL7.0的内容,这也是为VC6用户写的.现在VC8过时了,我觉得是时候更新这些文章包含VC7.1.(同时,VC7.1提供67的自动转换,但是我经常使用的并不顺利,所以VC7.1的用户尝试使用这些代码时可能会遇到困难).所以,当我浏览更新这些系列时,这些文章也将被更新,来反映新的WTL7.1的特性,同时我也在源代码下载里提供VC7.1工程的下载

Important note for VC 2005 users: The Express edition of VC 2005 does not come with ATL (or MFC for that matter) so you can't build ATL or WTL projects with the Express version.

If you are using VC 6, then you need the Platform SDK. You can't use WTL without it. You can use the web install version, or download the CAB files or an ISO image and run the setup locally. Be sure you use the utility to add the SDK include and lib directories to the VC search path. You can find this in the Visual Studio Registration folder in the Platform SDK program group. It's a good idea to get the latest Platform SDK even if you're using VC 7 so you have the latest headers and libs.

Vc2005用户注意:VC2005的简化版并没有提供ATL(由于这些原因也不提供MFC),所以你不能用简化版本来编译ATL或者是WTL工程.

如果你正在使用VC6, 你就需要SDK平台.没有它你不能使用WTL.你要可以使用网页安装版,或者是下载压缩文件或者是ISO镜像然后在本地安装的.确保你使用组件把sdk包含文件和库文件夹加入到vc的搜索路径里.你可以在SDK平台程序包里的Visual Studio Registration文件夹里.最好是使用最新的SDK平台,即使你使用vc7,这样你就可以使用最新的头文件和库了.

You need WTL. Download version 7.1 from Microsoft. See the articles "Introduction to WTL - Part 1" and "Easy installation of WTL" for some tips on installing the files. Those articles are rather out-of-date now, but still contain some good info. The WTL distribution also has a readme file with installation instructions. One thing which I don't think is mentioned in those articles is how to add the WTL files to the VC include path. In VC 6, click Tools|Options and go to the Directories tab. In the Show directories for combo box, select Include files. Then add a new entry that points to the directory where you put the WTL header files. In VC 7, click Tools|Options, click Projects then VC++ Directories. In the Show directories for combo box, select Include files. Then add a new entry that points to the directory where you put the WTL header files.

你需要WTL,下载7.1版本于Mircosoft.请阅读<WTL介绍-第一章><WTL简单安装>关于安装文件的一些建议.那些文章现在过时了,但是仍然包含许多有用的信息.WTL发布版也有一个包含有安装指南的readme文件,有一件事我没有考虑到的事在这些文件里被提到的就是怎样把WTL文件加到VC的包含文件路径里.VC6,点击工具/选项,然后转到文件夹选项.在显示文件的下拉框里,选择包含文件,,然后增加一个新的入口,它指向你把wtl文件所放的文件夹的.vc7,点击工具/选项,点击工程,然后,选择vc++文件,在显示文件夹下拉框里,选择包含文件,然后增加一个新的入口,指向wtl头文件所在目录.

Important: While we're on the subject of the VC 7 include path, you must make a change to the default directory list if you haven't updated your Platform SDK. Make sure that $(VCInstallDir)PlatformSDK/include is first in the list, above ($VCInstallDir)include, as shown here:

You need to know MFC, and know it well enough that you understand what's behind the message map macros, and can edit the code marked "DO NOT EDIT" with no problems.

重点:当我们在vc7.0的包含路径这个选项上时,如果你没有更新你的SDK平台,你必须改变它的默认文件夹.确保$(VCInstallDir)PlatformSDK/include在第一位.($VCInstallDir)include的上面.像下面所示:

---

你需要知道MFC,并且要理解消息map宏背后的东西.并且要能够编辑那些标记着”DO NOT EDIT”的代码而且不产生问题.

You need to know Win32 API programming, and know it well. If you learned Windows programming by going straight into MFC, without learning how messages work at the API level, you are unfortunately going to have trouble in WTL. If you don't know what a message's WPARAM and LPARAM mean, you should read other articles on API-level programming (there are lots of them here at CodeProject) so you understand.

你需要知道Win32 API编程,并且要非常了解它.如果你是直接进入MFC,而且不学习消息在API级别的工作来学习Windows编程,你非常不幸的将要在wtl中遇到麻烦了.如果你不知道消息的WPARAMLPARAM的意思.你应该读其它的关于API编程的文章(CodeProject中有大量的文件),

You need to know C++ template syntax. See the VC Forum FAQ for links to C++ FAQs and template FAQs.

你需要知道C++模板语法.参见VC论坛问题集

Since I haven't used VC 8 yet, I don't know if the sample code will compile on 8. Hopefully the 7-to-8 upgrade process will work better than the 6-to-7 process did. Please post on this article's forum if you have any trouble with VC 8.

自从我不用vc8以来,我不知道示例代码是否能够在vc8下编译通过.希望vc7vc8升级程序比vc6vc7升级程序管用.如果你在vc8下面遇到任何问题,请在些文章讨论里提示出来.

Introduction to the Series

系列介绍

WTL rocks. It does. It has a lot of the power of MFC's GUI classes, yet produces substantially smaller executables. If you're like me, and learned GUI programming with MFC, you've become quite comfortable with all the control wrappers MFC provides, as well as with the flexible message handling built into MFC. If you're also like me and don't relish the idea of several hundred K of MFC framework getting added to your programs, WTL is for you. However, there are a few barriers we have to overcome:

Wtl有大量的强大的mfc界面类.同时也产生本质上更小的执行程序.如果你像我一样,并且用MFC学习编程,你对mfc提供的控件包装,以及mfc内置的消息处理非常满意.如果你也像我一样,不满足向你的程序增加几个mfc框架.然而这里有一些障碍我们需要克服.

  • ATL-style templates look weird at first.
  • No ClassWizard support, so writing message maps involves some manual labor.
  • No documentation in MSDN, you need to find it somewhere else, or even look at the WTL source.
  • No reference books you can buy and keep on your shelf.
  • It has that "not officially supported by Microsoft" stigma
  • ATL/WTL windowing is sufficiently different from MFC windowing that not all your knowledge transfers over.
  • ATL模板看起来很奇怪
  • 没有类向导支持,所以写消息循环涉及到到许多手动工作.
  • MSDN中没有文档,你需要在别处找,或者要查找WTL源代码.
  • 没有相关的书藉你可以买来放在你的书架上.
  • 同时它也有不受Microsoft的支持.
  • ATL/WTL窗口也与mfc窗口完全不同,不是mfc中的知识所能转换的.

On the other hand, the benefits of WTL are:

另一方面,WTL的几个优点是:

  • No complex doc/view framework to learn or work around.
  • Has some essential UI features from MFC, like DDX/DDV and "update command UI" functionality.
  • Actually improves on some MFC features (e.g., more flexible splitter windows)
  • Much smaller executables compared to a statically-linked MFC app.
  • You can apply bug fixes to WTL yourself and not affect existing apps (compare this with replacing the MFC/CRT DLLs to fix a bug in one app, and having other apps break)
  • If you still need MFC, MFC and ATL/WTL windows can co-exist peacefully (for a prototype at work, I ended up creating an MFC CFrameWnd that contained a WTL CSplitterWindow, which contained MFC CDialogs -- that wasn't me showing off, it was modifying existing MFC code but using the nicer WTL splitter).
  • 没有复杂的文档/视图框架来学习和在它上面工作.
  • 有从mfc来的一些重要的UI特性,DDX/DDV”update command UI”功能
  • 实际上改进了某些mfc特征
  • 相比mfc的静态链接,它的执行程序比较小.
  • 你可以自己亲自修改bug,而且不影响已存在的app(相比在一个app里替换MFC/CRT   DLLs 来修改bug,却破坏了其它的apps)
  • 如果你仍然使用MFC.MFCATL/WTL窗口能够各平共存(为了一个原型能够工作,我最终产生一个mfcCFrameWnd,它包含一个WTLCSpliterWindows,它包含mfcCDialog.那不是我在炫耀,它是修改已存在的mfc代码,但是使用了更好的wtl的分割类)

In this series, I will first introduce the ATL windowing classes. WTL is, after all, a set of add-on classes to ATL, so a good understanding of ATL windowing is essential. Once I've covered that, I'll introduce WTL features and show how they make GUI programming a lot easier.

在这一系列中,我将首先介绍ATL窗口类.WTL是一个ATL的附加类集合.所以更好的理解ATL窗口类是很重要的.一但我涉及到它,我将介绍WTL特性,并且展示它们怎样让GUI编程更容易

Introduction to Part I

第一章介绍

WTL rocks. But before we get to why, we need to cover ATL first. WTL is a set of add-on classes to ATL, and if you've been strictly an MFC programmer in the past, you may never have encountered the ATL GUI classes. So please bear with me if I don't get to WTL right off the bat; the detour into ATL is necessary.

In this first part, I will give a little background on ATL, cover some essential points you need to know before writing ATL code, quickly explain those funky ATL templates, and cover the basic ATL windowing classes.

在我们开始之前,我们需要首先介绍ATL.WTLATL的附加类集合.并且如果你曾经是一个严格的MFC程序员,你可能永远不会遇到ATL界面类了.所以请允许我不按正常的WTL办事,绕路进入ATL是必需的.

在第一部分,我将对ATL背景做一个简单的介绍.包含在你写ATL代码之前需要知道的一些要点,快速的解释一些有趣的ATL模板和基本 ATL窗口类

ATL Background

ATL背景

ATL and WTL history

ATLWTL历史

The Active Template Library... kind of an odd name, isn't it? Old-timers might remember that it was originally the ActiveX Template Library, which is a more accurate name, since ATL's goal is to make writing COM objects and ActiveX controls easier. (ATL was also developed during the time that Microsoft was naming new products ActiveX-something, just as new products nowadays are called something.NET.) Since ATL is really about writing COM objects, it only has the most basic of GUI classes, the equivalent of MFC's CWnd and CDialog. Fortunately, the GUI classes are flexible enough to allow for something like WTL to be built on top of them.

活动模板库奇怪的名字,不是吗?老一辈人可能记得它的原来的名字是AxtiveX模板库,这个名字更准确,因为ATL的目标是是为了写com组件和ActiveX控件更容易.(ATL被开发出来的时候,正值微软把新产品称作ActiveX系列的时候.就像现在微软把新产品命名为XX.Net).因为atl确实是用来写com组件的,它只有大部分的基本gui类和与mfc相当的CWndCDialog.幸运的是,gui类很灵活,它允许在它之上建立像wtl这样的东西.

WTL had two major revisions as a Microsoft-owned project, numbered 3 and 7. (The version numbers were chosen to match the ATL version numbers, that's why they're not 1 and 2.) Version 3.1 is obsolete now so it will not be covered in this series. Version 7.0 was a major update to version 3, and version 7.1 added some bug fixes and minor features.

Wtl在微软自己的产品中有两个主要的版本,命名为37.(版本号和atl的版本号一致,那就是为什么不是12).版本3.1现在已经过时了,所以在这个专栏中不再涉及.版本7.0是版本3的主要更新,而版本7.1则修复了一些bug和增加了一些次要的特性.

Following version 7.1, Microsoft made WTL an open-source project, hosted at Sourceforge. The latest release from that site is version 7.5. I have not looked at 7.5 yet, so this series will not cover 7.5 at this time. (I'm already behind by two versions! I'll catch up eventually.)

接下来的版本7.1,软件是作为一个开源工程,放在Sourceforge.在那个网站上的最新版本是7.5,我现在还没有看版本7.5,所以此次这个专栏不会包含7.5.(我已经落后两个版本了!我肯定会赶上的)

ATL-style templates

Atl 模板

Even if you can read C++ templates without getting a headache, there are two things ATL does that might trip you up at first. Take this class for example:

即使你轻松的读完C++模板,一开始仍会遇到两个麻烦.看如下这个类:

Collapse Copy Code

class CMyWnd : public CWindowImpl<CMyWnd>

{

...

};

That actually is legal, because the C++ spec says that immediately after the class CMyWnd part, the name CMyWnd is defined and can be used in the inheritance list. The reason for having the class name as a template parameter is so ATL can do the second tricky thing, compile-time virtual function calls.

它实际上是合法的.因为C++细则指出.CMyWnd类部分,CMyWnd名字被定义之后可立即用于继承列表中.将类名作为模板参数的原因在于如此之后atl可以做一件狡猾的事情,就是编译时的参函数调用.

To see this in action, look at this set of classes:

为了来看实际应用,看这几个类:

Collapse Copy Code

template <class T>

class B1

{

public:

    void SayHi()

    {

    T* pT = static_cast<T*>(this);   // HUH?? I'll explain this below

 

        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;

}

The static_cast<T*>(this) is the trick here. It casts this, which is of type B1*, to either D1* or D2* depending on which specialization is being invoked. Because template code is generated at compile-time, this cast is guaranteed to be safe, as long as the inheritance list is written correctly. (If you wrote class D3 : public B1<D2> you'd be in trouble.) It's safe because the this object can only be of type D1* or D2* (as appropriate), and nothing else. Notice that this is almost exactly like normal C++ polymorphism, except that the SayHi() method isn't virtual.

static_cast<T*>(this)是一个陷阱.它把类型为B1*类型的this指针转换为D1*或者D2*类型,它取决于哪个特化被调用.因为模板代码是在编译期被生成的,只要继承列表写的正确,就能保证转换的类型安全.(如果这样写:class D3 : public B1<D2>,你可能就出错了.)它是类型安全的原因在于this对象的类型只能是D1或者D2,而不是其它的.注意和普通C++的多态很像,除了SayHi()方法不是虚函数.

To explain how this works, let's look at each call to SayHi(). In the first call, the specialization B1<D1> is being used, so the SayHi() code expands to:

为了解释这是怎样运行的.让我们看一下每一次的SayHi()调用.在第一次调用中,用的是特化B1<D1>,所以SayHi()代码展开如下:

Collapse Copy Code

void B1<D1>::SayHi()

{

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

 

    pT->PrintClassName();

}

Since D1 does not override PrintClassName(), D1's base classes are searched. B1 has a PrintClassName() method, so that is the one called.

因为D1没有重写PrintClassName(), D1的基类被找到,B1PrintClassName()方法,所以那个方法被调用.

Now, take the second call to SayHi(). This time, it's using the specialization B1<D2>, and SayHi() expands to:

现在,看到二次SayHi()方法的调用.这一次,使用的是特化B1<D1>,SayHi()的展开如下:

Collapse Copy Code

void B1<D2>::SayHi()

{

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

 

    pT->PrintClassName();

}

This time, D2 does contain a PrintClassName() method, so that is the one that gets called.

这一次,D2包含PrintClassName()方法,所以调用的就是那个方法.

The benefits of this technique are:

这个技术的好处是:

  • It doesn't require using pointers to objects.
  • It saves memory because there is no need for vtbls.
  • It's impossible to call a virtual function through a null pointer at runtime because of an uninitialized vtbl.
  • All function calls are resolved at compile time, so they can be optimized.
  • 不需要指向对象的指针
  • 节省内存,因为不需要虚表
  • 不能通过运行时的空指针调用虚函数,因为虚表还没有初始化
  • 所有函数的调用都发生在编译期,所以它们可以被优化

While the saving of a vtbl doesn't seem significant in this example (it would only be 4 bytes), think of the case where there are 15 base classes, some of those containing 20 methods, and the savings adds up.

节省虚表在这个例子中看起个来并不没有什么意义(它只有4字节).考虑有15个基类的例子,一些类包含20个方法,节省的数量就累加起来了.

ATL Windowing Classes

ATL窗口类

OK, enough background! Time to dive into ATL. ATL is designed with a strict interface/implementation division, and that's evident in the windowing classes. This is similar to COM, where interface definitions are completely separate from an implementation (or possibly several implementations).

好了,背景知识够了.是时候深入到atl.atl被设计的时候,实现的是严格的接口和实现.窗口类就是证明.这类似于com,它的接口定义和一个实现(或者也可能是多个)是完全隔离开的.

ATL has one class that defines the "interface" for a window, that is, what can be done with a window. This class is called CWindow. It is nothing more than a wrapper around an HWND, and it provides wrappers for almost all of the User32 APIs that take an HWND as the first parameter, such as SetWindowText() and DestroyWindow(). CWindow has a public member m_hWnd that you can access if you need the raw HWND. CWindow also has a operator HWND method, so you can pass a CWindow object to a function that takes an HWND. There is no equivalent to CWnd::GetSafeHwnd().

Atl有一个窗口接口的类的定义,也即是能够和窗口一起工作.这个类称作CWindow.它只不过是对HWND的包装,并且提供对几乎所有User32API的包装, HWND作为第一个参数,就像SetWindowText()DestroyWindow().CWindow有一个公有成员m_hWnd,如果你需要原始的HWND,你可以访问它.CWindow也有一个operator HWND方法,所以你可能给有一个HWND参数的函数传一个CWindow对象.它和CWnd:: GetSafeHwnd()不同.

CWindow is very different from MFC's CWnd. CWindow objects are inexpensive to create, since there is only one data member, and there is no equivalent to the object maps that MFC keeps internally to map HWNDs to CWnd objects. Also unlike CWnd, when a CWindow object goes out of scope, the associated window is not destroyed. This means you don't have to remember to detach any temp CWindow objects you might create.

CWindowmfcCWnd非常不同.CWindow对象的产生是非常廉价的,因为这里只有一个数据成员,并且也没有相应的从HWNDsCWnd的消息继承对象.同时,不像CWnd,当一个CWindow对象超出作用域时,相关联的窗口将不会销毁,这意味着你不需要记住你产生的窗口而去分离CWindow临时对象.

The ATL class that has the implementation of a window is CWindowImpl. CWindowImpl contains the code for such things as window class registration, window subclassing, message maps, and a basic WindowProc(). This is unlike MFC where everything is in one class, CWnd.

实现窗口实现的atl接口类是CWindowImpl.CWindowImpl包含诸如窗口类注册,窗口子类,消息映射和一个基本的WindowProc这样事情的代码.这不像mfc,它所有的事情都在一个类里面,就是CWnd.

There are also two separate classes that contain the implementation of a dialog box, CDialogImpl and CAxDialogImpl. CDialogImpl is used for plain dialogs, while CAxDialogImpl is used for dialogs that host ActiveX controls.

同时也有两个单独的类,它包含圣诞框的实现类.CDialogImplCAxDialogImpl.CDialogImpl用于普通对话框.同时,CAxDialogImpl用于包容ActiveX控件.

Defining a Window Implementation

定义一个窗口实现

Any non-dialog window you create will derive from CWindowImpl. Your new class needs to contain three things:

你产生的任何一个非对话框的窗口都继承于CWindowImpl.你的需要包含三件事:

  1. A window class definition
  2. A message map
  3. The default styles to use for the window, called the window traits
  4. 一个窗口类定义
  5. 一个消息映射
  6. 默认窗口类型,称作窗口特性

The window class definition is done using the DECLARE_WND_CLASS or DECLARE_WND_CLASS_EX macro. Both of these define an ATL struct CWndClassInfo that wraps the WNDCLASSEX struct. DECLARE_WND_CLASS lets you specify the new window class name and uses default values for the other members, while DECLARE_WND_CLASS_EX lets you also specify a class style and window background color. You can also use NULL for the class name, and ATL will generate a name for you.

窗口类的定义使用的是DECLARE_WND_CLASS或者DECLARE_WND_CLASS_EX.这两个宏都定义一个atl结构CWndClassInfo,它包装WNDCLASSEX结构.DECLARE_WND_CLASS让你指定一个新的窗口类的名字和对其它的数据数据成员使用默认的值.同时,DELCARE_WND_CLASS也可以让你指定一个类的风格和窗口的背景颜色.你也可以使用为类名使用NULL,atl将会为你生成一个名字.

Let's start out a new class definition, and I'll keep adding to it as we go through this section.

让我们以定义一个新的类开始,并且在我们进行这一部分的时候我们将持续的向里面加入东西.

Collapse Copy Code

class CMyWindow : public CWindowImpl<CMyWindow>

{

public:

    DECLARE_WND_CLASS(_T("My Window Class"))

};

Next comes the message map. ATL message maps are much simpler than MFC maps. An ATL map expands into a big switch statement; the switch looks for the right handler and calls the corresponding function. The macros for the message map are BEGIN_MSG_MAP and END_MSG_MAP. Let's add an empty map to our window.

接下来的是消息映射,atl的消息映射比mfc简单多了.一个atl的消息映射展开了就是一个大的开关语句.这个开关语句寻找正确的句柄并且调用相应的函数.消息映射的宏是BEGIN_MSG_MAPEND_MSG_MAP.让我们为我们的窗口加入一个空的消息映射.

Collapse Copy Code

class CMyWindow : public CWindowImpl<CMyWindow>

{

public:

    DECLARE_WND_CLASS(_T("My Window Class"))

 

    BEGIN_MSG_MAP(CMyWindow)

    END_MSG_MAP()

};

I'll cover how to add handlers to the map in the next section. Finally, we need to define the window traits for our class. Window traits are a combination of window styles and extended window styles that are used when creating the window. The styles are specified as template parameters so the caller doesn't have to be bothered with getting the styles right when it creates our window. Here's a sample traits definition using the ATL class CWinTraits:

我将在下一节增加对消息映射的处理.最终,我们为我们的类定义窗口特性,窗口特性是窗口风格和窗口扩展风格的联合,我们用它来产生窗口.窗口特性由模板参数指定,这样调用者在产生窗口的时候就不必受到窗口风格的烦恼.下面是使用atl类定义窗口特性的例子:

Collapse Copy Code

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

};

The caller can override the styles in the CMyWindowTraits definition, but generally this is not necessary. ATL also has a few predefined CWinTraits specializations, one of which is perfect for top-level windows like ours, CFrameWinTraits:

调用者可以在CMyWindowTraits的定义中重写风格.但是通常这不是必须的,atl也有少许CWinTraits预定义的特化.其中一个很适合像我们这样的顶层窗口CFrameWinTraits:

Collapse Copy Code

typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |

                     WS_CLIPSIBLINGS,

                   WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>

        CFrameWinTraits;

Filling in the message map

填充消息映射

The ATL message map is one area that is lacking in developer-friendliness, and something that WTL greatly improves on. ClassView does at least give you the ability to add message handers, however ATL doesn't have message-specific macros and automatic parameter unpacking like MFC does. In ATL, there are just three types of message handlers, one for WM_NOTIFY, one for WM_COMMAND, and one for everything else. Let's start by adding handlers for WM_CLOSE and WM_DESTROY to our window.

Atl消息映射是一个对开发者不太友好的区域,并且wtl极大的改善了.类视图至少可以给你增加消息处理的能力.然而,atl并不像mfc一样的消息定义宏和自动参数展开.atl,只有三种类型的消息处理.一个是WM_NOTIFY,一个是WM_COMMAND,另一个是处理其它用的.让我们给我们的窗口增加WM_CLOSEWM_DESTROY:

Collapse Copy Code

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;

    }

};

You'll notice that the handlers get the raw WPARAM and LPARAM values; you have to unpack them yourself when a message uses those parameters. There is also a fourth parameter, bHandled. This parameter is set to TRUE by ATL before the handler is called. If you want ATL's default WindowProc() to handle the message as well after your handler returns, you set bHandled to FALSE. This is different than MFC, where you have to explicitly call the base-class implementation of a message handler.

你会注意到,处理句柄得到原始的WPARAMLPARAM;当一个消息使用这些参数的时候你需要自己把它们取出来.同时这里有四个参数,bHandled.这个参数在atl调用之前设为真.如果你想使用atl的默认WindowProc来处理消息.你可以当处理返回时将bHandled设为FALSE.这不同于MFC,mfc里你必须详细的调用基本的消息处理实现.

Let's add a WM_COMMAND handler as well. Assuming our window's menu has an About item with ID IDC_ABOUT:

让我们增加一个WM_COMMAND处理.假设我们的窗口菜单有一个关于项,IDIDC_ABOUT:

Collapse Copy Code

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;

    }

};

Notice that the COMMAND_HANDLER macro does unpack the message parameters for you. The NOTIFY_HANDLER macro is similar, and unpacks the WM_NOTIFY message parameters.

注意到COMMAND_HANDLER宏并没有为你取出参数.NOTIFY_HANDLER宏也是类似,并且没有取出数据.

Advanced Message Maps and Mix-in Classes

高级消息映射和混和类

One major difference in ATL is that any C++ class can handle messages, unlike MFC where message-handling duties are split between CWnd and CCmdTarget, plus several classes that have a PreTranslateMessage() method. This ability lets us write what are commonly called "mix-in" classes, so that we can add features to our window simply by adding classes to the inheritance list.

Atl的一个主要不同之处在于信息化c++类能够处理消息,不像mfc的消息循环责任由CWndCCmdTarget分担.再加上几个有PreTranslateMessage()方法的类.这个能力让我们可以写出通常被称”mix-in”的类.所以我们可以简单的增加继承列表来增加特性:

A base class with a message map is usually a template that takes the derived class name as a template parameter, so it can access members of the derived class like m_hWnd (the HWND member in CWindow). Let's look at a mix-in class that paints the window background by handling WM_ERASEBKGND.

一个有消息映射的类通常是一个将类名作为模板参数的模板.所以它可以访问继承类的成员函数,m_hWnd(CWindowHWND成员).让我们看一个mix-in,它通过处理WM_ERASEBKGND来绘制窗口的背景.

Collapse Copy Code

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;

};

Let's go through this new class. First, CPaintBkgnd has two template parameters: the name of the derived class that is using CPaintBkgnd, and a color to use for the background. (The t_ prefix is often used for template parameters that are plain values.)

让我们来查看这个新类.产生,CPaintBkgnd有两个模板参数,一个是继承类的类名,一个是背景的颜色(t_为前缀的变量变量通常用作模板参数的参数变量)

The constructor and destructor are pretty simple, they create and destroy a brush of the color passed as t_crBrushColor. Next comes the message map, which handles WM_ERASEBKGND. Finally, there's the OnEraseBkgnd() handler which fills in the window with the brush created in the constructor. There are two things of note going on in OnEraseBkgnd(). First, it uses the derived class's window functions (namely GetClientRect()). How do we know that there even is a GetClientRect() in the derived class? The code wouldn't compile if there weren't! The compiler checks that the derived class T contains the methods that we call through the pT variable. Second, OnEraseBkgnd() has to unpack the device context from wParam.

构造函数和析构函数比较简单,它们产生和销毁一个颜色值为通过t_crBrushColor传入的画笔.下一步是消息映射,它处理了WM_ERASEBKGND.最后是OnEraseBkgnd()处理,它使用在构造函数中产生的画笔填充窗口.OnEraseBkgnd()中有两点要注意,首先,它使用窗口继承类的函数(称为GetClientRect()),我们怎样知道在继承类里一定有这个函数昵?如果没有的话,代码都不能编译通过,编译器检查继承类包含的方法.其次,OnEraseBkgnd()必须从wParam中取出设备上下文环境.

To use this mix-in class with our window, we do two things. First, we add it to the inheritance list:

为了在我们的窗口中使用mix-in,我们做两件事,首先,我们把它增加到继承列表中去:

Collapse Copy Code

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

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

Second, we need to get CMyWindow to pass messages along to CPaintBkgnd. This is called chaining message maps. In the CMyWindow message map, we add the CHAIN_MSG_MAP macro:

其次,我们需要使用CMyWindow来传递消息到CPaintBkgnd.这称为消息映射链.CMyWindow消息映射中,我们增加了CHAIN_MSG_MAP:

Collapse Copy Code

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

...

};

Any messages that reach the CHAIN_MSG_MAP line without being handled are passed on to the map in CPaintBkgnd. Note that WM_CLOSE, WM_DESTROY, and IDC_ABOUT will not be chained, because once those are handled, the message map search ends. The typedef is necessary because CHAIN_MSG_MAP is a preprocessor macro that takes one parameter; if we wrote CPaintBkgnd<CMyWindow, RGB(0,0,255)> as the parameter, the commas would make the preprocessor think that we're calling it with more than one parameter.

任何到达CHAIN_MSG_MAP而没有被处理的消息将被传递入CPaintBkgnd.注意到,WM_CLOSE,WM_DESTROYIDC_ABOUT将不会进入消息链,因为一旦那些消息被处理,消息映射的查找就结束了.typedef是必须的,因为CHAIN_MSG_MAP是一个带一个参数的处理器宏, 如果我们写CPaintBkgnd<CMyWindow, RGB(0, 0, 255)>作为参数,逗号将会使处理器认为我们调用它时使用了超过多个参数.

You could conceivably have several mix-in classes in the inheritance list, each with a CHAIN_MSG_MAP macro so that messages are passed to it. This is different from MFC, where each CWnd-derived class can have only one base class, and MFC automatically passes unhandled messages to the base class.

你能想像到在继承列表中有几个mix-in.每一个都有一个CHAIN_MSG_MAP宏可以让消息传递进去.这不同于MFC,它的每一个CWnd继承类只有一个基类,并且mfc自动的将未处理消息传递进基类,

Structure of an ATL EXE

一个atl可执行程序的结构

Now that we have a complete (if not entirely useful) main window, let's see how to use it in a program. ATL EXEs have one or more global variables that roughly correspond to the global CWinApp variable in an MFC program (normally called theApp). This area of ATL changed radically between VC6 and VC7, so I will cover the two versions separately.

现在我们有了一个完整的(虽然没有价值)主窗口,让我们看看怎样在程序中使用它.atl可执行程序有一个或多个与mfc中的全局的CWinApp变量(通常称为theApp)相应的全局变量.这个地方在vc6vc7中是完全不同的.所以我将分别讲这两个版本.

In VC 6

An ATL executable contains a global CComModule variable that must be called _Module. Here's how we start our stdafx.h:

一个atl可执行程序包含一个全局的CComModule变量,它必须称作_Module.下面是我们在stdafx.f:

Collapse Copy Code

// stdafx.h:

#define STRICT

#define WIN32_LEAN_AND_MEAN

 

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

extern CComModule _Module;  // Global _Module

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

atlbase.h will include the basic Windows headers, so there's no need to include windows.h, tchar.h, etc. In our CPP file, we declare the _Module variable:

atlbase.h包含基本的窗口头文件,所以这里就没有必要包含window.h,tchar.h.在我们的cpp文件中,我们声明_Module变量:

Collapse Copy Code

// main.cpp:

CComModule _Module;

CComModule has explicit initialization and shutdown functions that we need to call in WinMain(), so let's start with those:

CComModule有详细的初始化和停止函数,这些函数我们需要在WinMain()中调用.所以让我们从以下开始:

Collapse Copy Code

// main.cpp:

CComModule _Module;

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,

                   LPSTR szCmdLine, int nCmdShow)

{

    _Module.Init(NULL, hInst);

    _Module.Term();

}

The first parameter to Init() is only used in COM servers. Since our EXE is not a server, we can pass NULL. ATL doesn't provide its own WinMain() or message pump like MFC, so to get our program running, we create a CMyWindow object and add a message pump.

Init()的第一个参数只有用在Com服务器中.由于我们的EXE程序不是一个服务器,我们可以传入NULL.atl没有像mfc一样它自己的WinMain()或者消息泵,所以为了让我们的程序运行起来,我们产生了一个CMyWindow对象和增加了一个消息泵.

Collapse Copy Code

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

}

The only unusual thing in the above code is CWindow::rcDefault, which is a RECT member of CWindow. Using that as the window's initial RECT is like using CW_USEDEFAULT for the width and height with the CreateWindow() API.

以上代码的惟一的一件事就是CWindow::rcDdfault,它是CWindow的一个RECT成员.使用它作为窗口的初始化RECT就像使用CW_USEDEFAULT作为CreateWindow()API的长和宽.

Under the hood, ATL uses some assembly-language black magic to connect the main window's handle to its corresponding CMyWindow object. The upside of this is that there is no problem passing CWindow objects between threads, something that fails miserably with CWnds in MFC.

在底层,atl使用了一些汇编语言魔术般的把主窗口的句柄和它相应的CMyWindow()对象.这样做的一个优点是在线程间传递CWindow对象时没有任何问题,但是在MFC中使用CWnds对象时就会惨糟失败.

In VC 7

ATL 7 split up the module-management code into several classes. CComModule is still present for backwards-compatibility, but code written in VC 6 that is converted by VC 7 doesn't always compile cleanly (if at all) so I'll cover the new class here.

Atl7模块管理代码分到几个类中了.CComModule仍然是向后兼容的,但是,vc6写的代码转换到vc7仍然不能正确的编译.所以我将写几个新类.

In VC 7, the ATL headers automatically declare global instances of all the module classes, and the Init() and Term() methods are called for you, so those manual steps aren't necessary. Our stdafx.h therefore looks like:

vc7,atl头文件自动的声明了所有模块的全部实例,并且为你调用了Init()Term()方法.所以以下步骤不是必须的.因此我们的stdafx.h像以下一样:

Collapse Copy Code

// stdafx.h:

#define STRICT

#define WIN32_LEAN_AND_MEAN

 

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

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

The WinMain() function doesn't call any _Module methods, and looks like:

WinMain()功能不调用任何_Module方法,看起来如下:

Collapse Copy Code

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

}

Here's what our window looks like:

我们的窗口看起来如下:

Nothing particularly exciting, I'll admit. To spice it up, we'll add an About menu item that shows a dialog.

没有什么特别兴奋的,我承认.为了增加趣味,我将给显示的窗口增加一个关于菜单.

Dialogs in ATL

Atl的对话框

As mentioned earlier, ATL has two dialog classes. We'll use CDialogImpl for our about dialog. Making a new dialog class is almost like making a new frame window class; there are just two differences:

就像早先提过的一样,atl有两种对话框类,我们将为我们的关于圣诞框使用CDialogImpl.建立一个新的对话框几乎就像新建一个新的框架窗口类一样,,但是有两点不同:

  1. The base class is CDialogImpl instead of CWindowImpl.
  2. You need to define a public member called IDD that holds the resource ID of the dialog.
  3. 基类是CDialogImpl而不是CWindowImpl.
  4. 你需要定义一个称为IDD的公开成员,它是对话框的资源ID.

Here is the start of a new class definition for an about dialog:

下面是一个关于对话框类的初始定义:

Collapse Copy Code

class CAboutDlg : public CDialogImpl<CAboutDlg>

{

public:

    enum { IDD = IDD_ABOUT };

 

    BEGIN_MSG_MAP(CAboutDlg)

    END_MSG_MAP()

};

ATL has no built-in handlers for the OK and Cancel buttons, so we need to code them ourselves, along with a WM_CLOSE handler that is called if the user clicks the close button in the title bar. We also need to handle WM_INITDIALOG so that the keyboard focus is set properly when the dialog appears. Here is the complete class definition with message handlers.

Atl没有OKCancel按钮的内置处理句柄,所以我们需要自己来编码,在标题栏当用户点击关闭按钮的时候调用WM_CLOSE处理句柄.我们也要处理WM_INITDIALOG,这样当窗口出现显示的时候能合适的设置键盘焦点.这里是消息处理的完整类定义.

Collapse Copy Code

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;

    }

};

I used one handler for both OK and Cancel to demonstrate the wID parameter, which is set to either IDOK or IDCANCEL, depending on which button is clicked.

我使用一个带wID参数的消息处理来处理OKCancel来论述,这个参数被设为IDOKIDCANCEL,它取决哪个键被按下.

Showing the dialog is similar to MFC, you create an object of the new class and call DoModal(). Let's go back to our main window and add a menu with an About item that shows our new about dialog. We'll need to add two message handlers, one for WM_CREATE and one for the new menu item IDC_ABOUT.

显示对话框与mfc类似,你新建一个新类的对象,然后调用DoModal()方法,让我们回去我们的主窗口,增加一个关于菜单,来展示我位的关于对话框.我们增加两个消息处理,一个是WM_CREATE,另一个是为新菜单的IDC_ABOUT.

Collapse Copy Code

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;

    }

    // ...

};

One small difference in modal dialogs is where you specify the dialog's parent window. In MFC you pass the parent to the CDialog constructor, but in ATL you pass it as the first parameter to DoModal(). If you don't specify a window, as in the code above, ATL uses the result of GetActiveWindow() (which will be our frame window) as the parent.

在模态圣诞框的中一个小不同之处在在何处指定窗口的父窗口.mfc,你传递父窗口到CDialog的构造函数中,但是在atl中你得将父窗口作为DoModal()的第一个参数.如果你在以下的代码中没有指定窗口,atl使用GetActiveWindow()的返回值(它将是我们的框架窗口)作为父窗口.

The LoadMenu() call also demonstrates one of the CComModule methods, GetResourceInstance(). This returns the HINSTANCE of a module that holds resources, similar to AfxGetResourceHandle(). The default behavior is to return the HINSTANCE of the EXE. (There is also CComModule::GetModuleInstance(), which functions like AfxGetInstanceHandle().)

LoadMenu()的调用也说明了CComModule的一方法,就是GetResourceInstance().它返回一个模块的HINSTANCE,它持有资源,就像AfxGetResourceHandle().默认的返回是返回EXE的实例,(也有CComModule::GetModuleInstance().它类似AfxGetInstanceHandle()方法).

Note that OnCreate() is different between VC6 and 7, which is due to the different module-management classes. GetResourceInstance() is now in CAtlBaseModule, and we call the global _AtlBaseModule object that ATL sets up for us.

注意OnCreate()vc6v7中是不同的,这是由于模块管理类的不同导致的.GetResourceInstance()现在在CAtlBaseModule,并且我们调用的是atl为我们建立的全局的_AtlBaseModule对象.

Here is our revised main window and the about dialog:

下面是修订后的主窗口和关于对话框:

I'll Get to WTL, I Promise!

我将讲到wtl,我保证

But it will be in Part 2. Since I'm writing these articles for MFC developers, I felt that it was best to do an intro to ATL first, before diving into WTL. If this has been your first exposure to ATL, now might be a good time to write some simple apps on your own, so you get the hang of message maps and using mix-in classes. You can also experiment with ClassView's support for ATL message maps, as it can add message handlers for you. To get started in VC 6, right-click the CMyWindow item and pick Add Windows Message Handler on the context menu. In VC 7, right-click the CMyWindow item and pick Properties on the context menu. In the properties window, click the Messages button on the toolbar to see a list of window messages. You can add a handler for a message by going to its row, clicking the right column to turn it into a combo box, clicking the combo box arrow, then clicking the <Add> item in the drop-down list.

但是将会是在第二部分,因为我写些文章是为mfc开发者的,我觉得最好首先有必须介绍一些atl,在我们进入wtl之前.如果这还是你第一次接触atl,现在可能是好机会来写一些简单的程序了.这样你可以进行消息映射处理和混和使用mix-in.你可以从类视图里得到atl消息映射支持,因为它可以为你增加消息映射,为了在vc6中使用,右键点击CMyWindow,然后点击菜单上的窗口消息处理,vc7,右键点击CMyWindow,然右在菜单上点击属性项.在属性窗口,点击工具栏的消息按键,然后可以看见一列的窗口消息,你可以选择相应行选择相应的消息处理,点击右边的列,出现下拉框,点击下拉箭头,然后在下拉列表中点击增加项.

In Part II, I will cover the base WTL windowing classes, the WTL AppWizard, and the much nicer message map macros.

在第二部分,我将讲讲wtl的窗口类,wtl的类向导,和非常精妙的消息映射宏.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值