📄 wtl for mfc programmers, part i - atl gui classes - wtl.htm
字号:
RGB(0,0,255)>作为参数传递,那个“,”会使预处理器认为我们使用了多个参数。</P>
<P>你可以在继承列表中使用多个嵌入类,每一个嵌入类使用一个CHAIN_MSG_MAP宏,这样消息映射链就会将消息传递给它。这与MFC不同,MFC地CWnd派生类只能有一个基类,MFC自动将消息传递给基类。</P>
<H2><A name=exestructure></A><font color="#FFFF66">ATL程序的结构</font></H2>
<P>到目前为止我们已经有了一个完整地主窗口类(即使不完全有用),让我们看看如何在程序中使用它。一个ATL程序包含一个CComModule类型的全局变量_Module,这和MFC的程序都有一个CWinApp类型的全局变量theApp有些类似,唯一不同的是在ATL中这个变量必须命名为_Module。</P>
<P>下面是stdafx.h文件的开始部分:</P>
<PRE><SPAN class=cpp-comment><font color="#3300FF">// stdafx.h:</font></SPAN>
<font color="#3300FF"><SPAN class=cpp-preprocessor>#define STRICT</SPAN>
<SPAN class=cpp-preprocessor>#define VC_EXTRALEAN</SPAN>
<SPAN class=cpp-preprocessor>#include <atlbase.h> <font color="#000000"> // 基本的ATL类</font></SPAN>
<SPAN class=cpp-keyword>extern</SPAN> CComModule _Module; <SPAN class=cpp-comment><font color="#000000">// 全局_Module</font></SPAN>
<SPAN class=cpp-preprocessor>#include <atlwin.h> <font color="#000000"> // ATL窗口类</font></SPAN></font></PRE>
<P>atlbase.h已经包含最基本的Window编程的头文件,所以我们不需要在包含windows.h,tchar.h之类的头文件。在CPP文件中声明了_Module变量:</P>
<PRE><SPAN class=cpp-comment>// main.cpp:</SPAN>
<font color="#3300FF">CComModule _Module;</font></PRE>
<P>CComModule含有程序的初始化和关闭函数,需要在WinMain()中显示的调用,让我们从这里开始:</P>
<PRE><SPAN class=cpp-comment>// main.cpp:</SPAN>
<font color="#3300FF">CComModule _Module;</font>
<SPAN class=cpp-keyword><font color="#3300FF">int</font></SPAN><font color="#3300FF"> WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
LPSTR szCmdLine, <SPAN class=cpp-keyword>int</SPAN> nCmdShow)
{
_Module.Init(NULL, hInst);
_Module.Term();
}</font></PRE>
<P>Init()的第一个参数只有COM的服务程序才有用,由于我们的EXE不含有COM对象,我们只需将NULL传递给Init()就行了。ATL不提供自己的WinMain()和类似MFC的消息泵,所以我们需要创建CMyWindow对象并添加消息泵才能使我们的程序运行。<br>
</P>
<PRE><SPAN class=cpp-comment>// main.cpp:</SPAN>
<SPAN class=cpp-preprocessor><font color="#3300FF">#include "MyWindow.h"</font></SPAN><font color="#3300FF">
CComModule _Module;
<SPAN class=cpp-keyword>int</SPAN> WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
LPSTR szCmdLine, <SPAN class=cpp-keyword>int</SPAN> nCmdShow)
{
_Module.Init(NULL, hInst);
CMyWindow wndMain;
MSG msg;
<SPAN class=cpp-comment>// Create & show our main window</SPAN>
<SPAN class=cpp-keyword>if</SPAN> ( NULL == wndMain.Create ( NULL, CWindow::rcDefault,
_T(<SPAN class=cpp-string>"My First ATL Window"</SPAN>) ))
{
<SPAN class=cpp-comment>// Bad news, window creation failed</SPAN>
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>1</SPAN>;
}
wndMain.ShowWindow(nCmdShow);
wndMain.UpdateWindow();
<SPAN class=cpp-comment>// Run the message loop</SPAN>
<SPAN class=cpp-keyword>while</SPAN> ( GetMessage(&msg, NULL, <SPAN class=cpp-literal>0</SPAN>, <SPAN class=cpp-literal>0</SPAN>) > <SPAN class=cpp-literal>0</SPAN> )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
_Module.Term();
<SPAN class=cpp-keyword>return</SPAN> msg.wParam;
}</font></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="images/firstwin.png"
width=370 align=bottom border=0></P>
<P>我得承认这确实没有什么激动人心的地方。我们将添加一个About菜单并显示一个对话框,主要是为它增加一些情趣。</P>
<h2><A name=dialogs></A><font color="#FFFF66">ATL中的对话框</font></H2>
<P>我们前面提到过,ATL有两个对话框类,我们的About对话框使用CDialogImpl。生成一个新对话框和生成一个主窗口几乎一样,只有两点不同:<br>
</P>
<OL>
<LI>窗口的基类是CDialogImpl而不是CWindowImpl。
<LI>你需要定义名称为IDD的公有成员用来保存对话框资源的ID。 </LI>
</OL>
<P>现在开始为About对话框定义一个新类:</P>
<PRE><SPAN class=cpp-keyword><font color="#3300FF">class</font></SPAN><font color="#3300FF"> CAboutDlg : <SPAN class=cpp-keyword>public</SPAN> CDialogImpl<CAboutDlg>
{
<SPAN class=cpp-keyword>public</SPAN>:
<SPAN class=cpp-keyword>enum</SPAN> { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
END_MSG_MAP()
};</font></PRE>
<P>ATL没有在内部实现对“OK”和“Cancel”两个按钮的响应处理,所以我们需要自己添加这些代码,如果用户用鼠标点击标题栏的关闭按钮,WM_CLOSE的响应函数就会被调用。我们还需要处理WM_INITDIALOG消息,这样我们就能够在对话框出现时正确的设置键盘焦点,下面是完整的类定义和消息响应函数。</P>
<PRE><SPAN class=cpp-keyword><font color="#3300FF">class</font></SPAN><font color="#3300FF"> CAboutDlg : <SPAN class=cpp-keyword>public</SPAN> CDialogImpl<CAboutDlg>
{
<SPAN class=cpp-keyword>public</SPAN>:
<SPAN class=cpp-keyword>enum</SPAN> { 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();
<SPAN class=cpp-keyword>return</SPAN> TRUE; <SPAN class=cpp-comment>// let the system set the focus</SPAN>
}
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
EndDialog(IDCANCEL);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
LRESULT OnOKCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
};</font></PRE>
<P>我使用一个消息响应函数同时处理“OK”和“Cancel”两个按钮的WM_COMMAND消息,因为命令响应函数的wID参数就已经指明了消息是来自“OK”按钮还是来自“Cancel”按钮。</P>
<P>显示对话框的方法与MFC相似,创建一个新对话框类的实例,然后调用DoModal()方法。现在我们返回主窗口,添加一个带有About菜单项的菜单用来显示我们的对话框,这需要再添加两个消息响应函数,一个是响应<code>WM_CREATE</code>,另一个是响应菜单的IDC_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> 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>public</SPAN>:
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
<SPAN class=cpp-comment>// ...</SPAN>
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 );
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
CAboutDlg dlg;
dlg.DoModal();
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
<SPAN class=cpp-comment>// ...</SPAN>
};</font></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="images/firstabout.png"
width=369 align=bottom border=0></P>
<H2><A name=whereswtlman></A><font color="#FFFF66">我会继续讲WTL,我保证!</font></H2>
<P>我会继续讲WTL的,只是会在第二部分。我觉得既然这些文章是写给使用MFC的开发者的,所以有必要在投入WTL之前先介绍一些ATL。如果你是第一次接触到ATL,那现在你就可以尝试写一些小程序,处理消息和使用嵌入类,你也可以尝试用类向导支持消息映射链,使它能够自动添加消息响应。现在就开始,右键单击CMyWindow项,在弹出的上下文菜单中单击“<i>Add
Windows Message Handler</i>”菜单项。</P>
<P>在第二部分,我将全面介绍基本的WTL窗口类和一个更好的消息映射宏。</P>
<H2><A name=revisionhistory></A><font color="#FFFF66">修改记录</font></H2>
<P>2003年3月22日,本文第一次发表。</P>
<P> </P>
<P> </P>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -