﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Thinking in C++-文章分类-WTL</title><link>http://www.cppblog.com/yishanhante/category/3723.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 23:48:56 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 23:48:56 GMT</pubDate><ttl>60</ttl><item><title>MFC程序员的WTL指南: Part I - ATL 界面类[转]</title><link>http://www.cppblog.com/yishanhante/articles/19482.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Fri, 09 Mar 2007 03:46:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19482.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19482.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19482.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19482.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19482.html</trackback:ping><description><![CDATA[
		<b>README.TXT</b>
		<p>在你开始使用WTL或着在本文章的讨论区张贴消息之前，我想请你先阅读下面的材料。</p>
		<p>你需要开发平台SDK（Platform SDK）。你要使用WTL不能没有它，你可以使用<a href="http://www.microsoft.com/msdownload/platformsdk/sdkupdate/"><font color="#002c99">在线升级</font></a>安装开发平台SDK，也可以<a href="http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdk-full.htm"><font color="#002c99">下载全部文件</font></a>后在本地安装。在使用之前要将SDK的包含文件（.h头文件）和库文件（.Lib文件）路径添加到VC的搜索目录，SDK有现成的工具完成这个工作，这个工具位于开发平台SDK程序组的“<i>Visual Studio Registration</i>”文件夹里。</p>
		<p>你需要安装 WTL。你可以从微软的网站上<a href="http://download.microsoft.com/download/VisualStudioNET/Install/7.0/WXP/EN-US/WTL70.exe"><font color="#002c99">下载WTL的7.0版</font></a>，(*编辑注：WTL也可以到VC知识库<a href="http://www.vckbase.com/tools" target="_blank"><font color="#002c99">http://www.vckbase.com/tools</font></a>下载) 在安装之前可以先查看“<a href="http://www.codeproject.com/wtl/wtlintro1.asp"><font color="#002c99">Introduction to WTL - Part 1</font></a>”和“<a href="http://www.codeproject.com/wtl/wtlinst.asp"><font color="#002c99">Easy installation of WTL</font></a>”这两篇文章，了解一下所要安装的文件的信息，虽然现在这些文章有些过时，但还是可以提供很多有用的信息。有一件我认为不该在本篇文章中提到的事是告诉VC如何搜索WTL的包含文件路径，如果你用的VC6，用鼠标点击 <i>Tools\Options</i>，转到<i>Directories</i>标签页，在显示路径的列表框中选择<i>Include Files</i>，然后将WTL的包含文件的存放路径添加到包含文件搜索路径列表中。</p>
		<p>你需要了解MFC。很好地了解MFC将有助于你理解后面提到的有关消息映射的宏并能够编辑那些标有“不要编辑（DO NOT EDIT）”的代码而不会出现问题。</p>
		<p>你需要清楚地知道如何使用Win32 API编程。如果你是直接从MFC开始学习Windows编程，没有学过API级别的消息处理方式，那很不幸你会在使用WTL时遇到麻烦。如果不了解Windows消息中WPARAM参数和LPARAM参数的意义，应该明白需要读一些这方面的文章（在CodeProject有大量的此类文章）。</p>
		<p>你需要知道 C++ 模板的语法，你可以到<a href="http://www.codeproject.com/cpp/cppforumfaq.asp#other_cpp"><font color="#002c99">VC Forum FAQ</font></a> 相关的连接寻求答案。</p>
		<p>我只是讨论了一些涵盖VC 6的特点，不过据我了解所有的程序都可以在VC 7上使用。由于我不使用VC 7，我无法对那些在VC 7中出现的问题提供帮助，不过你还是可以放心的在此张贴你的问题，因为其他的人可能会帮助你。</p>
		<b>
				<a name="seriesintro">
				</a>对本系列文章的总体介绍</b>
		<p>WTL 具有两面性，确实是这样的。它没有MFC的界面（GUI）类库那样功能强大，但是能够生成很小的可执行文件。如果你象我一样使用MFC进行界面编程，你会觉得MFC提供的界面控件封装使用起来非常舒服，更不用说MFC内置的消息处理机制。当然，如果你也象我一样不希望自己的程序仅仅因为使用了MFC的框架就增加几百K的大小的话，WTL就是你的选择。当然，我们还要克服一些障碍：</p>
		<ul>
				<li>ATL样式的模板类初看起来有点怪异 
</li>
				<li>没有类向导的支持，所以要手工处理所有的消息映射。 
</li>
				<li>MSDN没有正式的文档支持，你需要到处去收集有关的文档，甚至是查看WTL的源代码。 
</li>
				<li>买不到参考书籍 
</li>
				<li>没有微软的官方支持 
</li>
				<li>ATL/WTL的窗口与MFC的窗口有很大的不同，你所了解的有关MFC的知识并不全部适用与WTL。 </li>
		</ul>
		<p>从另一方面讲，WTL也有它自身的优势：</p>
		<ul>
				<li>不需要学习或掌握复杂的文档/视图框架。 
</li>
				<li>具有MFC的基本的界面特色，比如DDX/DDV和命令状态的自动更新功能（译者加：比如菜单的Check标记和Enable标记）。 
</li>
				<li>增强了一些MFC的特性（比如更加易用的分隔窗口）。 
</li>
				<li>可生成比静态链接的MFC程序更小的可执行文件（译者加：WTL的所有源代码都是静态链接到你的程序中的）。 
</li>
				<li>你可以修正自己使用的WTL中的错误（BUG）而不会影响其他的应用程序(相比之下，如果你修正了有BUG的MFC/CRT动态库就可能会引起其它应用程序的崩溃。 <br /></li>
				<li>如果你仍然需要使用MFC，MFC的窗口和ATL/WTL的窗口可以“和平共处”。（例如我工作中的一个原型就使用了了MFC的CFrameWnd，并在其内包含了WTL的CSplitterWindow，在CSplitterWindow中又使用了MFC的CDialogs -- 我并不是为了炫耀什么，只是修改了MFC的代码使之能够使用WTL的分割窗口，它比MFC的分割窗口好的多）。 <br /></li>
		</ul>
		<p>在这一系列文章中，我将首先介绍ATL的窗口类，毕竟WTL是构建与ATL之上的一系列附加类，所以需要很好的了解ATL的窗口类。介绍完ATL之后我将介绍WTL的特性以并展示它是如何使界面编程变得轻而易举。</p>
		<b>
				<a name="intopart1">
				</a>对第一章的简单介绍</b>
		<p>WTL是个很酷的工具，在理解这一点之前需要首先介绍ATL。WTL是构建与ATL之上的一系列附加类，如果你是个严格使用MFC的程序员那么你可能没有机会接触到ATL的界面类，所以请容忍我在开始WTL之前先罗索一些别的东西，绕道来介绍一下ATL是很有必要地。</p>
		<p>在本文的第一部分，我将给出一点ATL的背景知识，包括一些编写ATL代码必须知道的基本知识，快速的解释一些令人不知所措的ATL模板类和基本的ATL窗口类。</p>
		<b>
				<a name="atlbackground">
				</a>ATL 背景知识</b>
		<b>
				<a name="atlhistory">
				</a>ATL 和 WTL 的发展历史</b>
		<p>“活动模板库”（Active Template Library）是一个很古怪的名字，不是吗？那些年纪大的人可能还记得它最初被称为“网络组件模板库”，这可能是它更准确的称呼，因为ATL的目的就是使编写组件对象和ActiveX控件更容易一些（ATL是在微软开发新产品ActiveX-某某的过程中开发的，那些ActiveX-某某现在被称为某某.NET）。由于ATL是为了便于编写组件对象而存在的，所以只提供了简单的界面类，相当于MFC的窗口类（CWnd）和对话框类（CDialog）。幸运的是这些类非常的灵活，能够在其基础上构建象WTL这样的附加类。</p>
		<p>WTL现在已经是第二次修正了，最初的版本是3.1，现在的版本是7（WTL的版本号之所以这样选择是为了与ATL的版本匹配，所以不存在1和2这样的版本号）。WTL 3.1可以与VC 6和VC 7一起使用，但是在VC 7下需要定义几个预处理标号。WTL 7向下兼容WTL 3.1，并且不作任何修改就可以与VC 7一起使用，现在看来没有任何理由还使用3.1来进行新的开发工作。<br /></p>
		<b>
				<a name="atltemplates">
				</a>ATL-style 模板</b>
		<p>即使你能够毫不费力地阅读C++的模板类代码，仍然有两件事可能会使你有些头晕，以下面这个类的定义为例：</p>
		<pre>class  CMyWnd : public CWindowImpl&lt;CMyWnd&gt;
{
    ...
};  </pre>
		<p>这样作是合法的，因为C++的语法解释说即使CMyWnd类只是被部分定义，类名CMyWnd已经被列入递归继承列表，是可以使用的。将类名作为模板类的参数是因为ATL要做另一件诡秘的事情，那就是编译期间的虚函数调用机制。</p>
		<p>如果你想要了解它是如何工作地，请看下面的例子：</p>
		<pre>template &lt;class T&gt;
class B1
{
public: 
    void SayHi() 
    {
        T* pT = static_cast&lt;T*&gt;(this);   // HUH?? 我将在下面解释
 
        pT-&gt;PrintClassName();
    }
protected:
    void PrintClassName() { cout &lt;&lt; "This is B1"; }
};
 
class D1 : public B1&lt;D1&gt;
{
    // No overridden functions at all
};
 
class D2 : public B1&lt;D2&gt;
{
protected:
    void PrintClassName() { cout &lt;&lt; "This is D2"; }
};
 
main()
{
    D1 d1;
    D2 d2;
 
    d1.SayHi();    // prints "This is B1"
    d2.SayHi();    // prints "This is D2"
}</pre>
		<p>这句代码static_cast&lt;T*&gt;(this) 就是窍门所在。它根据函数调用时的特殊处理将指向B1类型的指针this指派为D1或D2类型的指针，因为模板代码是在编译其间生成的，所以只要编译器生成正确的继承列表，这样指派就是安全的。（如果你写成：</p>
		<pre>class D3 : public B1&lt;D2&gt;</pre>
		<p>就会有麻烦) 之所以安全是因为this对象只可能是指向D1或D2（在某些情况下）类型的对象，不会是其他的东西。注意这很像C++的多态性（polymorphism），只是SayHi()方法不是虚函数。</p>
		<p>要解释这是如何工作的，首先看对每个SayHi()函数的调用，在第一个函数调用，对象B1被指派为D1，所以代码被解释成：</p>
		<pre>void B1&lt;D1&gt;::SayHi()
{
    D1* pT = static_cast&lt;D1*&gt;(this);
 
    pT-&gt;PrintClassName();
}</pre>
		<p>由于D1没有重载PrintClassName()，所以查看基类B1，B1有PrintClassName()，所以B1的PrintClassName()被调用。</p>
		<p>现在看第二个函数调用SayHi()，这一次对象被指派为D2类型，SayHi()被解释成：</p>
		<pre>void B1&lt;D2&gt;::SayHi()
{
    D2* pT = static_cast&lt;D2*&gt;(this);
 
    pT-&gt;PrintClassName();
}</pre>
		<p>这一次，D2含有PrintClassName()方法，所以D2的PrintClassName()方法被调用。</p>
		<p>这种技术的有利之处在于：</p>
		<ul>
				<li>不需要使用指向对象的指针。 
</li>
				<li>节省内存，因为不需要虚函数表。 
</li>
				<li>因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数。 
</li>
				<li>所有的函数调用在编译时确定（译者加：区别于C++的虚函数机制使用的动态编连），有利于编译程序对代码的优化。 </li>
		</ul>
		<p>节省虚函数表在这个例子中看起来无足轻重（每个虚函数只有4个字节），但是设想一下如果有15个基类，每个类含有20个方法，加起来就相当可观了。</p>
		<b>
				<a name="atlwindowing">
				</a>ATL 窗口类</b>
		<p>好了，关于ATL的背景知识已经讲的构多了，到了该正式讲ATL的时候了。ATL在设计时接口定义和实现是严格区分开的，这在窗口类的设计中是最明显的，这一点类似于COM，COM的接口定义和实现是完全分开的（或者可能有多个实现）。</p>
		<p>ATL有一个专门为窗口设计的接口，可以做全部的窗口操作，这就是CWindow。它实际上就是对HWND操作的包装类，对几乎所有以HWND句柄为第一个参数的窗口API的进行了封装，例如：SetWindowText() 和 DestroyWindow()。CWindow类有一个公有成员m_hWnd，使你可以直接对窗口的句柄操作，CWindow还有一个操作符HWND，你可以讲CWindow对象传递给以HWND为参数的函数，但这与CWnd::GetSafeHwnd()（译者加：MFC的方法）没有任何等同之处。</p>
		<p>CWindow 与 MFC 的CWnd类有很大的不同，创建一个CWindow对象占用很少的资源，因为只有一个数据成员，没有MFC窗口中的对象链，MFC内部维持这一个对象链，此对象链将HWND映射到CWnd对象。还有一点与MFC的CWnd类不同的是当一个CWindow对象超出了作用域，它关联的窗口并不被销毁掉，这意味着你并不需要随时记得分离你所创建的临时CWindow对象。</p>
		<p>在ATL类中对窗口过程的实现是CWindowImpl。CWindowImpl 含有所有窗口实现代码，例如：窗口类的注册，窗口的子类化，消息映射以及基本的WindowProc()函数，可以看出这与MFC的设计有很大的不同，MFC将所有的代码都放在一个CWnd类中。</p>
		<p>还有两个独立的类包含对话框的实现，它们分别是CDialogImpl 和 CAxDialogImpl，CDialogImpl 用于实现普通的对话框而CAxDialogImpl实现含有ActiveX控件的对话框。</p>
		<b>
				<a name="windowimpl">
				</a>定义一个窗口的实现</b>
		<p>任何非对话框窗口都是从CWindowImpl 派生的，你的新类需要包含三件事情：</p>
		<ol>
				<li>一个窗口类的定义 
</li>
				<li>一个消息映射链 
</li>
				<li>窗口使用的默认窗口类型，称为<i>window traits</i></li>
		</ol>
		<p>窗口类的定义通过DECLARE_WND_CLASS宏或DECLARE_WND_CLASS_EX宏来实现。这辆个宏定义了一个CWndClassInfo结构，这个结构封装了WNDCLASSEX结构。DECLARE_WND_CLASS宏让你指定窗口类的类名，其他参数使用默认设置，而DECLARE_WND_CLASS_EX宏还允许你指定窗口类的类型和窗口的背景颜色，你也可以用NULL作为类名，ATL会自动为你生成一个类名。</p>
		<p>让我们开始定义一个新类，在后面的章节我会逐步的完成这个类的定义。</p>
		<pre>class CMyWindow : public CWindowImpl&lt;CMyWindow&gt;
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))
};</pre>
		<p>接下来是消息映射链，ATL的消息映射链比MFC的简单的多，ATL的消息映射链被展开为switch语句，switch语句正确的消息处理者并调用相应的函数。使用消息映射链的宏是BEGIN_MSG_MAP 和 END_MSG_MAP，让我们为我们的窗口添加一个空的消息映射链。</p>
		<pre>class CMyWindow : public CWindowImpl&lt;CMyWindow&gt;
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))
 
    BEGIN_MSG_MAP(CMyWindow)
    END_MSG_MAP()
};</pre>
		<p>我将在下一节展开讲如何如何添加消息处理到消息映射链。最后，我们需要为我们的窗口类定义窗口的特征，窗口的特征就是窗口类型和扩展窗口类型的联合体，用于创建窗口时指定窗口的类型。窗口类型被指定为参数模板，所以窗口的调用者不需要为指定窗口的正确类型而烦心，下面是是同ATL类CWinTraits定义窗口类型的例子：</p>
		<pre>typedef CWinTraits&lt;WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW&gt; CMyWindowTraits;
 
class CMyWindow : public CWindowImpl&lt;CMyWindow, CWindow, CMyWindowTraits&gt;
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))
 
    BEGIN_MSG_MAP(CMyWindow)
    END_MSG_MAP()
};</pre>
		<p>调用者可以重载CMyWindowTraits的类型定义，但是一般情况下这是没有必要的，ATL提供了几个预先定义的特殊的类型，其中之一就是CFrameWinTraits，一个非常棒的框架窗口：</p>
		<pre>typedef CWinTraits&lt;WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,<br />                                         WS_EX_APPWINDOW | WS_EX_WINDOWEDGE&gt;  CFrameWinTraits;</pre>
		<b>
				<a name="msgmap">
				</a>填写消息映射链</b>
		<p>ATL的消息映射链是对开发者不太友好的部分，也是WTL对其改进最大的部分。类向导至少可以让你添加消息响应，然而ATL没有消息相关的宏和象MFC那样的参数自动展开功能，在ATL中只有三种类型的消息处理，一个是WM_NOTIFY，一个是WM_COMMAND，第三类是其他窗口消息，让我们开始为我们的窗口添加WM_CLOSE 和 WM_DESTROY的消息相应函数。</p>
		<pre>class CMyWindow : public CWindowImpl&lt;CMyWindow, CWindow, CFrameWinTraits&gt;
{
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&amp; bHandled)
    {
        DestroyWindow();
        return 0;
    }
 
    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&amp; bHandled)
    {
        PostQuitMessage(0);
        return 0;
    }
};</pre>
		<p>你可能注意到消息响应函数的到的是原始的WPARAM 和 LPARAM值，你需要自己将其展开为相应的消息所需要的参数。还有第四个参数bHandled，这个参数在消息相应函数调用被ATL设置为TRUE，如果在你的消息响应处理完之后需要ATL调用默认的WindowProc()处理该消息，你可以讲bHandled设置为FALSE。这与MFC不同，MFC是显示的调用基类的响应函数来实现的默认的消息处理的。</p>
		<p>让我们也添加一个对WM_COMMAND消息的处理，假设我们的窗口有一个ID为IDC_ABOUT的About菜单：</p>
		<pre>class CMyWindow : public CWindowImpl&lt;CMyWindow, CWindow, CFrameWinTraits&gt;
{
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&amp; bHandled)
    {
        DestroyWindow();
        return 0;
    }
 
    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&amp; bHandled)
    {
        PostQuitMessage(0);
        return 0;
    }
 
    LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL&amp; bHandled)
    {
        MessageBox ( _T("Sample ATL window"), _T("About MyWindow") );
        return 0;
    }
};</pre>
		<p>需要注意得是COMMAND_HANDLER宏已经将消息的参数展开了，同样，NOTIFY_HANDLER宏也将WM_NOTIFY消息的参数展开了。</p>
		<b>
				<a name="advmsgmap">
				</a>高级消息映射链和嵌入类</b>
		<p>ATL的另一个显著不同之处就是任何一个C++类都可以响应消息，而MFC只是将消息响应任务分给了CWnd类和CCmdTarget类，外加几个有<code>PreTranslateMessage()</code>方法的类。ATL的这种特性允许我们编写所谓的“嵌入类”，为我们的窗口添加特性只需将该类添加到继承列表中就行了，就这么简单！</p>
		<p>一个基本的带有消息映射链的类通常是模板类，将派生类的类名作为模板的参数，这样它就可以访问派生类中的成员，比如m_hWnd（CWindow类中的HWND成员）。让我们来看一个嵌入类的例子，这个嵌入类通过响应<code>WM_ERASEBKGND</code>消息来画窗口的背景。</p>
		<pre>template &lt;class T, COLORREF t_crBrushColor&gt;
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&amp; bHandled)
    {
        T*   pT = static_cast&lt;T*&gt;(this);
        HDC  dc = (HDC) wParam;
        RECT rcClient;
 
        pT-&gt;GetClientRect ( &amp;rcClient );
        FillRect ( dc, &amp;rcClient, m_hbrBkgnd );
        return 1;    // we painted the background
    }
 
protected:
    HBRUSH m_hbrBkgnd;
};</pre>
		<p>让我们来研究一下这个新类。首先，CPaintBkgnd有两个模板参数：使用CPaintBkgnd的派生类的名字和用来画窗口背景的颜色。（t_ 前缀通常用来作为模板类的模板参数的前缀）CPaintBkgnd也是从CMessageMap派生的，这并不是必须的，因为所有需要响应消息的类只需使用<code>BEGIN_MSG_MAP</code>宏就足够了，所以你可能看到其他的一些嵌入类的例子代码，它们并不是从该基类派生的。</p>
		<p>构造函数和析构函数都相当简单，只是创建和销毁Windows画刷，这个画刷由参数t_crBrushColor决定颜色。接着是消息映射链，它响应WM_ERASEBKGND消息，最后由响应函数OnEraseBkgnd()用构造函数创建的画刷填充窗口的背景。在OnEraseBkgnd()中有两件事需要注意，一个是它使用了一个派生的窗口类的方法（即GetClientRect()），我们如何知道派生类中有GetClientRect()方法呢？如果派生类中没有这个方法我们的代码也不会有任何抱怨，由编译器确认派生类T是从CWindow派生的。另一个是OnEraseBkgnd()没有将消息参数wParam展开为设备上下文（DC）。（WTL最终会解决这个问题，我们很快就可以看到，我保证）<br /></p>
		<p>要在我们的窗口中使用这个嵌入类需要做两件事：首先，将它加入到继承列表：</p>
		<pre>class CMyWindow : public CWindowImpl&lt;CMyWindow, CWindow, CFrameWinTraits&gt;,
                  public CPaintBkgnd&lt;CMyWindow, RGB(0,0,255)&gt;</pre>
		<p>其次，需要CMyWindow将消息传递给CPaintBkgnd，就是将其链入到消息映射链，在CMyWindow的消息映射链中加入CHAIN_MSG_MAP宏：</p>
		<pre>class CMyWindow : public CWindowImpl&lt;CMyWindow, CWindow, CFrameWinTraits&gt;,
                  public CPaintBkgnd&lt;CMyWindow, RGB(0,0,255)&gt; 
{
...
typedef CPaintBkgnd&lt;CMyWindow, RGB(0,0,255)&gt; 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()
...
};</pre>
		<p>任何CMyWindow没有处理的消息都被传递给CPaintBkgnd。应该注意的是WM_CLOSE，WM_DESTROY和IDC_ABOUT消息将不会传递，因为这些消息一旦被处理消息映射链的查找就会中止。使用typedef是必要地，因为宏是预处理宏，只能有一个参数，如果我们将CPaintBkgnd&lt;CMyWindow, RGB(0,0,255)&gt;作为参数传递，那个“,”会使预处理器认为我们使用了多个参数。</p>
		<p>你可以在继承列表中使用多个嵌入类，每一个嵌入类使用一个CHAIN_MSG_MAP宏，这样消息映射链就会将消息传递给它。这与MFC不同，MFC地CWnd派生类只能有一个基类，MFC自动将消息传递给基类。</p>
		<b>
				<a name="exestructure">
				</a>ATL程序的结构</b>
		<p>到目前为止我们已经有了一个完整地主窗口类（即使不完全有用），让我们看看如何在程序中使用它。一个ATL程序包含一个CComModule类型的全局变量_Module，这和MFC的程序都有一个CWinApp类型的全局变量theApp有些类似，唯一不同的是在ATL中这个变量必须命名为_Module。</p>
		<p>下面是stdafx.h文件的开始部分：</p>
		<pre>// stdafx.h:
<span class="cpp-preprocessor">#define STRICT
<span class="cpp-preprocessor">#define VC_EXTRALEAN
 
<span class="cpp-preprocessor">#include &lt;atlbase.h&gt;        // 基本的ATL类
extern CComModule _Module;  // 全局_Module
<span class="cpp-preprocessor">#include &lt;atlwin.h&gt;         // ATL窗口类</span></span></span></span></pre>
		<p>atlbase.h已经包含最基本的Window编程的头文件，所以我们不需要在包含windows.h，tchar.h之类的头文件。在CPP文件中声明了_Module变量：</p>
		<pre>// main.cpp:
CComModule _Module;</pre>
		<p>CComModule含有程序的初始化和关闭函数，需要在WinMain()中显示的调用，让我们从这里开始：</p>
		<pre>// main.cpp:
CComModule _Module;
 
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
                   LPSTR szCmdLine, int nCmdShow)
{
    _Module.Init(NULL, hInst);
    _Module.Term();
}</pre>
		<p>Init()的第一个参数只有COM的服务程序才有用，由于我们的EXE不含有COM对象，我们只需将NULL传递给Init()就行了。ATL不提供自己的WinMain()和类似MFC的消息泵，所以我们需要创建CMyWindow对象并添加消息泵才能使我们的程序运行。<br /></p>
		<pre>// main.cpp:
<span class="cpp-preprocessor">#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 &amp; 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(&amp;msg, NULL, 0, 0) &gt; 0 )
    {
        TranslateMessage(&amp;msg);
        DispatchMessage(&amp;msg);
    }
 
    _Module.Term();
    return msg.wParam;
}</span></pre>
		<p>上面的代码唯一需要说明的是CWindow::rcDefault，这是CWindow中的成员（静态数据成员），数据类型是RECT。和调用CreateWindow() API时使用CW_USEDEFAULT指定窗口的宽度和高度一样，ATL使用rcDefault作为窗口的最初大小。</p>
		<p>在ATL代码内部，ATL使用了一些类似汇编语言的魔法将主窗口的句柄与相应的CMyWindow对象联系起来，在外部看来就是可以毫无问题的在线程之间传递CWindow对象，而MFC的CWnd却不能这样作。</p>
		<p>这就是我们的窗口：</p>
		<p>
				<img height="249" alt=" [First ATL window - 4K] " src="http://www.vckbase.com/document/journal/vckbase37/images/firstwin.png" width="370" align="bottom" border="0" />
		</p>
		<p>我得承认这确实没有什么激动人心的地方。我们将添加一个About菜单并显示一个对话框，主要是为它增加一些情趣。</p>
		<b>
				<a name="dialogs">
				</a>ATL中的对话框</b>
		<p>我们前面提到过，ATL有两个对话框类，我们的About对话框使用CDialogImpl。生成一个新对话框和生成一个主窗口几乎一样，只有两点不同：<br /></p>
		<ol>
				<li>窗口的基类是CDialogImpl而不是CWindowImpl。 
</li>
				<li>你需要定义名称为IDD的公有成员用来保存对话框资源的ID。 </li>
		</ol>
		<p>现在开始为About对话框定义一个新类：</p>
		<pre>class CAboutDlg : public CDialogImpl&lt;CAboutDlg&gt;
{
public:
    enum { IDD = IDD_ABOUT };
 
    BEGIN_MSG_MAP(CAboutDlg)
    END_MSG_MAP()
};</pre>
		<p>ATL没有在内部实现对“OK”和“Cancel”两个按钮的响应处理，所以我们需要自己添加这些代码，如果用户用鼠标点击标题栏的关闭按钮，WM_CLOSE的响应函数就会被调用。我们还需要处理WM_INITDIALOG消息，这样我们就能够在对话框出现时正确的设置键盘焦点，下面是完整的类定义和消息响应函数。</p>
		<pre>class CAboutDlg : public CDialogImpl&lt;CAboutDlg&gt;
{
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&amp; bHandled)
    {
        CenterWindow();
        return TRUE;    // let the system set the focus
    }
 
    LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&amp; bHandled)
    {
        EndDialog(IDCANCEL);
        return 0;
    }
 
    LRESULT OnOKCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL&amp; bHandled)
    {
        EndDialog(wID);
        return 0;
    }
};</pre>
		<p>我使用一个消息响应函数同时处理“OK”和“Cancel”两个按钮的WM_COMMAND消息，因为命令响应函数的wID参数就已经指明了消息是来自“OK”按钮还是来自“Cancel”按钮。</p>
		<p>显示对话框的方法与MFC相似，创建一个新对话框类的实例，然后调用DoModal()方法。现在我们返回主窗口，添加一个带有About菜单项的菜单用来显示我们的对话框，这需要再添加两个消息响应函数，一个是响应<code>WM_CREATE</code>，另一个是响应菜单的IDC_ABOUT命令。</p>
		<pre>class CMyWindow : public CWindowImpl&lt;CMyWindow, CWindow, CFrameWinTraits&gt;,
                  public CPaintBkgnd&lt;CMyWindow,RGB(0,0,255)&gt;
{
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&amp; bHandled)
    {
    HMENU hmenu = LoadMenu ( _Module.GetResourceInstance(),
                             MAKEINTRESOURCE(IDR_MENU1) );
 
        SetMenu ( hmenu );
        return 0;
    }
 
    LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL&amp; bHandled)
    {
    CAboutDlg dlg;
 
        dlg.DoModal();
        return 0;
    }
    // ...
};</pre>
		<p>在指定对话框的父窗口的方式上有些不同，MFC是通过构造函数将父窗口的指针传递给对话框而在ATL中是将父窗口的指针作为DoModal()方法的第一个参数传递给对话框的，如果象上面的代码一样没有指定父窗口，ATL会使用<code>GetActiveWindow()</code>得到的窗口（也就是我们的主框架窗口）作为对话框的父窗口。</p>
		<p>对LoadMenu()方法的调用展示了CComModule的另一个方法－GetResourceInstance()，它返回你的EXE的HINSTANCE实例，和MFC的AfxGetResourceHandle()方法相似。（当然还有CComModule::GetModuleInstance()，它相当于MFC的AfxGetInstanceHandle()。）</p>
		<p>这就是主窗口和对话框的显示效果：</p>
		<p>
				<img height="248" alt=" [About box - 5K] " src="http://www.vckbase.com/document/journal/vckbase37/images/firstabout.png" width="369" align="bottom" border="0" />
		</p>
		<b>
				<a name="whereswtlman">
				</a>我会继续讲WTL，我保证!</b>
		<p>我会继续讲WTL的，只是会在第二部分。我觉得既然这些文章是写给使用MFC的开发者的，所以有必要在投入WTL之前先介绍一些ATL。如果你是第一次接触到ATL，那现在你就可以尝试写一些小程序，处理消息和使用嵌入类，你也可以尝试用类向导支持消息映射链，使它能够自动添加消息响应。现在就开始，右键单击CMyWindow项，在弹出的上下文菜单中单击“<i>Add Windows Message Handler</i>”菜单项。</p>
		<p>在第二部分，我将全面介绍基本的WTL窗口类和一个更好的消息映射宏。</p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/19482.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-09 11:46 <a href="http://www.cppblog.com/yishanhante/articles/19482.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MFC程序员的WTL指南: 中文版序言[转]</title><link>http://www.cppblog.com/yishanhante/articles/19481.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Fri, 09 Mar 2007 03:43:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19481.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19481.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19481.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19481.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19481.html</trackback:ping><description><![CDATA[我一直在寻找这样一个类库：他对Windows的窗口提供面向对象的封装，有灵活的消息响应机制和比较完备的界面框架解决方案，对标准控件提供简练实用的封装，支持操作系统的新特性，支持功能扩充和二次开发，有代码自动生成向导机制，生成的程序使用较少的系统资源，最后是有完全的代码支持和文档支持。 <br /><br />你会说那就用MFC吧！ <br /><br />是的，我一直使用MFC，但我对MFC已经越来越厌倦了。陈旧的类库使得它无法支持操作系统的新特性(MFC的类库从4.21版之后就没有更新了，而那时是1998年，人们使用Windows 95和windows NT4)，臃肿的消息映射机制和为了兼容性而保留下来的代码使得程序效率低下，面面俱到的框架结构使得生成的应用程序庞大并占用过多的系统资源。当一个功能简单的程序使用动态链接也超过200K，占用3%-4%的系统资源时，我决定放弃MFC，寻找一个新的功能类似的类库。我研究过很多类似的代码，不是过于简单，无法用于应用程序的开发就是缺乏代码和文档的支持。在CodeProject上有一个名为Class的类库，我也研究过它的代码，具备了基本的界面框架，对控件也有了简单的封装，但是不实用，庞大的虚函数机制使得对象非常臃肿，无法减少对资源的占用。我甚至仿照MFC做了一个简单的类库miniGUI，形成了基本的框架解决方案，但是最后放弃了，原因很简单：无法用于应用程序的开发。一个应用程序界面框架错综复杂，要考虑的事情太多，开发者不可能在应用程序和界面框架两线作战。就在我即将绝望的时候，我遇到了WTL。 <br /><br />由于工作的需要经常开发一些COM组件，在要求不能使用MFC的场合就是用ATL。ATL提供了对窗口的面向对象地封装和简单的消息映射机制，但是ATL过于简单，用它开发应用程序几乎不可能。要想让ATL具备界面框架解决方案的功能还需要做很多事情，幸运的是WTL就做了这些事情。WTL是个很奇特的东西，它由微软公司一群热情的程序员维护，它从未出现在微软的官方产品名单上，但可以从微软的官方网站下载最新的WTL。它没有正式的文档支持，用WTL做关键字在MSDN中检索只能得到0个结果，但是全世界的开发网站上都有针对WTL的讨论组和邮件列表，任何问题都会得到热情的解答。我认真地对比了MFC和WTL，发现二者有很多相通之处，MFC的功能几乎都能在WTL中实现，只是方法不同而已。我几乎不费吹灰之力就将以前写的一个MFC程序用WTL改写了，使用静态链接的WTL程序比使用动态链接的MFC程序还要小，资源占用只有MFC程序的一半。 <br /><br />但是一时的热情不能解决文档缺乏的困扰，虽然网上有很多使用WTL的例子和说明文章，几乎把MFC能实现的各种稀奇古怪的效果都实现了，但都是叛塾诰植课侍獾媒饩觯狈ο低车厝娴亟樯躓TL的文章。就在这个时候我看到了迈克尔.敦(Michael Dunn)的“WTL for MFC Programmers”系列文章，我的感觉和1995年我第一次见到MSDN时一样，几乎是迫不及待地将其读完，同时也萌发了将其翻译成汉语的冲动。于是给Michael写了封邮件，希望能够得到授权将他的文章翻译成汉语(事实上在这之前我已经翻译了两章了)。在得到授权确认后才发现这个工作是多么的困难，但为时已晚，只能硬着头皮撑下去。 <br />现在介绍一下迈克尔.敦这个人。迈克(Mike)住在阳光灿烂的洛杉矶，深受那里天气的宠爱使他愿意一直住在那里。他在4年级时就开始在Apple //e上编程序，1995年从<a href="http://www.ucla.edu/">UCLA</a> (加利福尼亚大学洛杉矶分校)毕业，获得数学学士学位。毕业后加盟赛门铁克(Symantec)公司，成为Norton AntiVirus小组的质量评价工程师。他几乎是自学了Windows和MFC编程，1999年他为Norton AntiVirus 2000设计并编写了新的界面。迈克现在是<a href="http://www.pressplay.com/">pressplay</a>(不久成为<a href="http://www.napster.com/">Napster</a>) 的开发人员。他最近开发了一个IE的工具条插件<a href="http://www.ultrabar.com/">UltraBar</a> ，可以轻松实现繁琐的网络搜索功能。他还和别人合作创办了一家软件开发公司：<a href="http://www.zabersoft.com/">Zabersoft</a> ，该公司在洛杉矶和欧登赛(丹麦)都设有办事处。迈克喜欢玩弹球和骑自行车，偶尔也玩一下PlayStation，他还一直坚持学习法语，官方汉语和日语。 <br />另外需要说明得是我翻译“WTL for MFC Programmers”系列文章不是为了获得任何利益，只是想为大家提供一些新的思路。如果你是MFC的坚定捍卫者，看到这里你就可以停下来了，再看下去是浪费你的时间(希望你看了前面几段文字还能挺住不要呕吐)。如果你是个对另类事物充满热情的程序员，你不能不研究WTL，它真的是一座宝藏最后用我的朋友对我的翻译文章的评价来结束“WTL for MFC Programmers”中文版的序言：翻译水平和你用的鼠标一样烂！<br /><br /><img src ="http://www.cppblog.com/yishanhante/aggbug/19481.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-09 11:43 <a href="http://www.cppblog.com/yishanhante/articles/19481.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>