📄 wtl for mfc programmers, part i - atl gui classes - wtl.htm
字号:
D2* pT = <SPAN class=cpp-keyword>static_cast</SPAN><D2*>(<SPAN class=cpp-keyword>this</SPAN>);
pT->PrintClassName();
}</font></PRE>
<P>这一次,D2含有PrintClassName()方法,所以D2的PrintClassName()方法被调用。</P>
<P>这种技术的有利之处在于:</P>
<UL>
<LI>不需要使用指向对象的指针。
<LI>节省内存,因为不需要虚函数表。
<LI>因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数。
<LI>所有的函数调用在编译时确定(译者加:区别于C++的虚函数机制使用的动态编连),有利于编译程序对代码的优化。</LI>
</UL>
<P>节省虚函数表在这个例子中看起来无足轻重(每个虚函数只有4个字节),但是设想一下如果有15个基类,每个类含有20个方法,加起来就相当可观了。</P>
<H2><A name=atlwindowing></A><font color="#FFFF66">ATL 窗口类</font></H2>
<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>
<H2><A name=windowimpl></A><font color="#FFFF66">定义一个窗口的实现</font></H2>
<P>任何非对话框窗口都是从CWindowImpl 派生的,你的新类需要包含三件事情:</P>
<OL>
<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><SPAN class=cpp-keyword><font color="#3300FF">class</font></SPAN><font color="#3300FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CMyWindow>
{
<SPAN class=cpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=cpp-string>"My Window Class"</SPAN>))
};</font></PRE>
<P>接下来是消息映射链,ATL的消息映射链比MFC的简单的多,ATL的消息映射链被展开为switch语句,switch语句正确的消息处理者并调用相应的函数。使用消息映射链的宏是BEGIN_MSG_MAP
和 END_MSG_MAP,让我们为我们的窗口添加一个空的消息映射链。</P>
<PRE><SPAN class=cpp-keyword><font color="#3300FF">class</font></SPAN><font color="#3300FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CMyWindow>
{
<SPAN class=cpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=cpp-string>"My Window Class"</SPAN>))
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};</font></PRE>
<P>我将在下一节展开讲如何如何添加消息处理到消息映射链。最后,我们需要为我们的窗口类定义窗口的特征,窗口的特征就是窗口类型和扩展窗口类型的联合体,用于创建窗口时指定窗口的类型。窗口类型被指定为参数模板,所以窗口的调用者不需要为指定窗口的正确类型而烦心,下面是是同ATL类CWinTraits定义窗口类型的例子:</P>
<PRE><SPAN class=cpp-keyword><font color="#3300FF">typedef</font></SPAN><font color="#3300FF"> CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW> CMyWindowTraits;
<SPAN class=cpp-keyword>class</SPAN> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
{
<SPAN class=cpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=cpp-string>"My Window Class"</SPAN>))
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};</font></PRE>
<P>调用者可以重载CMyWindowTraits的类型定义,但是一般情况下这是没有必要的,ATL提供了几个预先定义的特殊的类型,其中之一就是CFrameWinTraits,一个非常棒的框架窗口:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">typedef</font></SPAN><font color="#0000FF"> CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,<br> WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CFrameWinTraits;</font></PRE>
<H3><a name=msgmap></a><font color="#FFFF66">填写消息映射链</font></H3>
<P>ATL的消息映射链是对开发者不太友好的部分,也是WTL对其改进最大的部分。类向导至少可以让你添加消息响应,然而ATL没有消息相关的宏和象MFC那样的参数自动展开功能,在ATL中只有三种类型的消息处理,一个是WM_NOTIFY,一个是WM_COMMAND,第三类是其他窗口消息,让我们开始为我们的窗口添加WM_CLOSE
和 WM_DESTROY的消息相应函数。</P>
<PRE><SPAN class=cpp-keyword><font color="#3300FF">class</font></SPAN><font color="#3300FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
{
<SPAN class=cpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=cpp-string>"My Window Class"</SPAN>))
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();
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(<SPAN class=cpp-literal>0</SPAN>);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
};</font></PRE>
<P>你可能注意到消息响应函数的到的是原始的WPARAM 和 LPARAM值,你需要自己将其展开为相应的消息所需要的参数。还有第四个参数bHandled,这个参数在消息相应函数调用被ATL设置为TRUE,如果在你的消息响应处理完之后需要ATL调用默认的WindowProc()处理该消息,你可以讲bHandled设置为FALSE。这与MFC不同,MFC是显示的调用基类的响应函数来实现的默认的消息处理的。</P>
<P>让我们也添加一个对WM_COMMAND消息的处理,假设我们的窗口有一个ID为IDC_ABOUT的About菜单:</P>
<PRE><SPAN class=cpp-keyword><font color="#3300FF">class</font></SPAN><font color="#3300FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
{
<SPAN class=cpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=cpp-string>"My Window Class"</SPAN>))
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();
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(<SPAN class=cpp-literal>0</SPAN>);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
MessageBox ( _T(<SPAN class=cpp-string>"Sample ATL window"</SPAN>), _T(<SPAN class=cpp-string>"About MyWindow"</SPAN>) );
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
};</font></PRE>
<P>需要注意得是COMMAND_HANDLER宏已经将消息的参数展开了,同样,NOTIFY_HANDLER宏也将WM_NOTIFY消息的参数展开了。</P>
<H2><A name=advmsgmap></A><font color="#FFFF66">高级消息映射链和嵌入类</font></H2>
<P>ATL的另一个显著不同之处就是任何一个C++类都可以响应消息,而MFC只是将消息响应任务分给了CWnd类和CCmdTarget类,外加几个有<code>PreTranslateMessage()</code>方法的类。ATL的这种特性允许我们编写所谓的“嵌入类”,为我们的窗口添加特性只需将该类添加到继承列表中就行了,就这么简单!</P>
<P>一个基本的带有消息映射链的类通常是模板类,将派生类的类名作为模板的参数,这样它就可以访问派生类中的成员,比如m_hWnd(CWindow类中的HWND成员)。让我们来看一个嵌入类的例子,这个嵌入类通过响应<code>WM_ERASEBKGND</code>消息来画窗口的背景。</P>
<PRE><SPAN class=cpp-keyword><font color="#3300FF">template</font></SPAN> <font color="#3300FF"><<SPAN class=cpp-keyword>class</SPAN> T, COLORREF t_crBrushColor>
<SPAN class=cpp-keyword>class</SPAN> CPaintBkgnd : <SPAN class=cpp-keyword>public</SPAN> CMessageMap
{
<SPAN class=cpp-keyword>public</SPAN>:
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 = <SPAN class=cpp-keyword>static_cast</SPAN><T*>(<SPAN class=cpp-keyword>this</SPAN>);
HDC dc = (HDC) wParam;
RECT rcClient;
pT->GetClientRect ( &rcClient );
FillRect ( dc, &rcClient, m_hbrBkgnd );
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>1</SPAN>; <SPAN class=cpp-comment>// we painted the background</SPAN>
}
<SPAN class=cpp-keyword>protected</SPAN>:
HBRUSH m_hbrBkgnd;
};</font></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><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
<SPAN class=cpp-keyword>public</SPAN> CPaintBkgnd<CMyWindow, RGB(<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>255</SPAN>)></font></PRE>
<P>其次,需要CMyWindow将消息传递给CPaintBkgnd,就是将其链入到消息映射链,在CMyWindow的消息映射链中加入CHAIN_MSG_MAP宏:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
<SPAN class=cpp-keyword>public</SPAN> CPaintBkgnd<CMyWindow, RGB(<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>255</SPAN>)>
{
...
<SPAN class=cpp-keyword>typedef</SPAN> CPaintBkgnd<CMyWindow, RGB(<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>255</SPAN>)> 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()
...
};</font></PRE>
<P>任何CMyWindow没有处理的消息都被传递给CPaintBkgnd。应该注意的是WM_CLOSE,WM_DESTROY和IDC_ABOUT消息将不会传递,因为这些消息一旦被处理消息映射链的查找就会中止。使用typedef是必要地,因为宏是预处理宏,只能有一个参数,如果我们将CPaintBkgnd<CMyWindow,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -