第二部分 - WTL 中的 GUI 基础类
内容
? ? ? ? ?
第二部分介绍 WTL 综述
开始一个 WTL EXE WTL 消息映射的增强
使用 WTL AppWizard 可以得到什么
? ? ?
通历向导(VC 6) 通历向导(VC 7) 检查生成的代码
? ? ? ?
CMessageLoop 内幕 CFrameWindowImpl 内幕 回到时钟程序 UI 更新
? ?
控制时钟的新菜单项 调用 UIEnable() ? ? ?
关于消息映射的最后注意事项 下一站,1995 修订历史
第二部分介绍
好,是实实在在地讲述 WTL 的时候了!在这部分里,我会介绍写一个主框架窗口的基础知识,以及 WTL 引入的比较受欢迎的改进,比如 UI 更新和更好的消息影射。为了最大程度地掌握本部分的内容,你应该安装 WTL 以使其头文件处于 VC 的搜索路径中,而且 AppWizard 也在适当的目录下。WTL 的分发包中附有如何安装 AppWizard 的说明,请参考该文档。
记住,如果你安装 WTL 或者编译示例代码时遇到了任何问题,请在张贴你的问题之前阅读第一部分的 ReadMe 一节。
WTL 综述
WTL 的类可以分为几个主要的类别:
1. 框架窗口的实现 - CFrameWindowImpl, CMDIFrameWindowImpl 2. 控件封装 - CButton, CListViewCtrl 3. GDI 封装 - CDC, CMenu 4. 特殊的 UI 特性
- CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw 5. 工具类以及宏 - CString, CRect, BEGIN_MSG_MAP_EX
本文将深入到框架窗口中去,顺便提及一些 UI 特性和工具类。大多数的类都是独立的,不过也有一些像 CDialogResize 这样的嵌入类(mix-in)。
开始一个 WTL EXE
如果你不使用 WTL AppWizard (稍后我们就会提到它),那么一个 WTL EXE 一开始会很像一个 ATL EXE。如同第一部分中的那样,本文中的示例代码是另一个框架窗口,不过为了展示一些 WTL 的特性,较之前者不再那么微不足道。 在本节里,我们会从头开始一个新的 EXE。主窗口会在其客户区显示当前的时间。下面是一个基本的 stdafx.h:
#define STRICT
#define WIN32_LEAN_AND_MEAN #define _WTL_USE_CSTRING
#include
extern CAppModule _Module; // WTL version of CComModule #include
#include
(注意,我们需要一个全局的 CAppModule 变量尽管在第一部分里这不是必需的。CAppModule 的一些特性与我们所需的空闲处理以及 UI 更新相关,所以我们需要 CAppModule 的存在)
接下来我们来定义我们的框架窗口。像我们这样的 SDI 窗口继承
自 CFrameWindowImpl。窗口类名是使用 DECLARE_FRAME_WND_CLASS 而不是DECLARE_WND_CLASS 来定义。这儿是 MyWindow.h 里我们窗口定义的开头: // MyWindow.h:
class CMyWindow : public CFrameWindowImpl
public:
DECLARE_FRAME_WND_CLASS(_T(\),
IDR_MAINFRAME);
BEGIN_MSG_MAP(CMyWindow)
CHAIN_MSG_MAP(CFrameWindowImpl
DECLARE_FRAME_WND_CLASS 有两个参数,窗口类名(可以为 NULL,ATL 会替你生成一个类名),和一个资源 ID。WTL 会根据此 ID 去寻找图标、菜单以及加速键表,并在窗口创建时加载它们。还会根据此 ID 寻找一个字符串,然后使用该串作为窗口的标题。我们还把消息串联到CFrameWindowImpl,因为它有自己的一些消息处理器,尤其是 WM_SIZE 和 WM_DESTROY。
现在我们来看 WinMain()。它和第一部分中的 WinMain() 极其类似,只是创建主窗口的调用存在差异。
// main.cpp:
#include \#include \
CAppModule _Module;
int APIENTRY WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow ) {
_Module.Init ( NULL, hInstance );
CMyWindow wndMain; MSG msg;
// Create the main window
if ( NULL == wndMain.CreateEx() )
return 1; // Window creation failed
// Show the window
wndMain.ShowWindow ( nCmdShow ); wndMain.UpdateWindow();
// Standard Win32 message loop
while ( GetMessage ( &msg, NULL, 0, 0 ) > 0 ) {
TranslateMessage ( &msg ); DispatchMessage ( &msg ); }
_Module.Term(); return msg.wParam;
}
CFrameWindowImpl 的 CreateEx() 方法采用了最常用的缺省值,因而我们不需要指定任何参数。CFrameWindowImpl 还会处理前文提到的资源加载事宜,所以现在你应该使用 IDR_MAINFRAME 这一 ID 生成一些伪资源,或者使用随本文附带的示例代码。
如果你马上运行,就可以看到主框架窗口了,当然,它实际上还没有做任何事情。我们需要加入一些消息处理器来干活儿,所以现在是研究 WTL 消息映射宏的好时机。
WTL 消息映射的增强
在使用 Win32 API 时,既令人讨厌又易于出错的事情之一就是从随消息一起发送过来的 WPARAM 和 LPARAM 数据中拆封参数。不幸的是,ATL 并未提供更多的帮助,除去 WM_COMMAND 和 WM_NOTIFY 之外,我们仍然需要从其他所有的消息中拆封数据。不过,WTL 正好在这儿对我们施以援手!
WTL 的增强消息映射宏在 atlcrack.h 文件中(此名字来源于 “message cracker”,是一个应用于 windowsx.h 中类似的宏的术语)。要使用这些宏的第一个步骤在 VC 6 和 VC 7 里是不一样的,在 atlcrack.h 中的以下提示解释了这一不同:
对于 ATL 3.0,使用了解拆处理器的消息映射必须使用 BEGIN_MSG_MAP_EX。 对于 ATL 7.0/7.1,你可以为 CWindowImpl/CDialogImpl 的派生类使
用 BEGIN_MSG_MAP,但是对于不是派生于CWindowImpl/CDialogImplbut 的类则必须使用 BEGIN_MSG_MAP_EX。
所以,如果你在使用 VC 6,你需要这样改动你的 MyWindow.h: // MyWindow.h, VC6 only:
class CMyWindow : public CFrameWindowImpl
public:
BEGIN_MSG_MAP_EX(CMyWindow)
CHAIN_MSG_MAP(CFrameWindowImpl
};
(_EX 宏对于 VC 6 来讲是必需的,因为包含于其中的某些代码是消息处理器宏需要使用的。出于可读性的原因,这里就不列出 VC 6 和 VC 7 版本的头文件了,因为它们仅仅是一个宏上面的不同。只需记住_EX 宏在 VC 7 里是不需要的即可。)
对我们的时钟程序来说,我们需要处理 WM_CREATE 并设置一个定时器。WTL 把针对一个消息的消息处理器命名为 MSG_ 后随消息名,比如MSG_WM_CREATE。这些宏仅接受处理器的名字。我们来为 WM_CREATE 添加一个处理器:
class CMyWindow : public CFrameWindowImpl
public:
BEGIN_MSG_MAP_EX(CMyWindow) MSG_WM_CREATE(OnCreate)
CHAIN_MSG_MAP(CFrameWindowImpl
// OnCreate(...) ? };
WTL 的消息处理器看起来很像 MFC,每个处理器都根据随消息传入的参数有一个不同的原型。不过,由于没有向导来写处理器,我们不得不自己来查找原型。幸运的是 VC 可以帮上忙。将光标(注:此处原文错误,不应该是光标[cursor],而应该是插入符[caret])放在 “MSG_WM_CREATE” 文本上再按 F12 会转到宏的定义处。在 VC 6 里,VC 会先重新编译工程以构建浏览信息数据库。这一工作一旦完成,VC 就会在 MSG_WM_CREATE 的定义处打开 atlcrack.h: #define MSG_WM_CREATE(func) \\ if (uMsg == WM_CREATE) \\ { \\
SetMsgHandled(TRUE); \\
lResult = (LRESULT)func((LPCREATESTRUCT)lParam); \\ if(IsMsgHandled()) \\ return TRUE; \\
}
带下划线的是最重要的一行,那是对处理器的实际调用,它告诉我们处理器会返回一个 LRESULT 并接受一个 LPCREATESTRUCT 类型的参数。注意,没有像 ATL 的宏所使用的 bHandled 参数。SetMsgHandled() 函数替代了该参数,很快我们就要解释这件事情。
现在我们可以为窗口类添加一个 OnCreate() 处理器: