用WTL构建HTML界面的应用程序(2)

来源:百度文库 编辑:神马文学网 时间:2024/04/29 11:18:07
前文已经提到,WTL不是框架。因此,它并不隐藏winmain函数。而且,WTL向导确实也在主程序中生成一段代码,整理如下:
CAppModule _Module;
int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT){
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
CMainFrame wndMain;
if(wndMain.CreateEx() == NULL){
ATLTRACE(_T("Main window creation failed!\n"));
return 0;
}
wndMain.ShowWindow(nCmdShow);
int nRet = theLoop.Run();
_Module.RemoveMessageLoop();
return nRet;
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
HRESULT hRes = ::CoInitialize(NULL);
ATLASSERT(SUCCEEDED(hRes));
hRes = _Module.Init(NULL, hInstance);
ATLASSERT(SUCCEEDED(hRes));
AtlAxWinInit();
// XXX 额外的初始化代码可以插到这里
int nRet = Run(lpstrCmdLine, nCmdShow);
_Module.Term();
::CoUninitialize();
return nRet;
}
上述代码进行了一些修改,使其不再适应Windows 9x环境。很容易知道,这段程序的基本流程是:
初始化COM环境(CoInitialize)
初始化ATL应用程序
注册ATL窗口类
(运行应用程序:显示窗口,进入消息循环,结束)
终止应用程序线程
撤销COM环境
对于我们来说,最关心的是运行应用程序这一步。有些时候,可能会希望在真正显示窗口之前做点什么(例如,检查注册表中的相关配置信息,等等)。这些代码通常应写在_tWinMain函数中调用Run之前。
具体在什么地方执行呢?这取决于代码本身的性质。通常的经验,在XXX标记的地方插入代码是比较合适的。这是因为,此时COM环境已经被初始化,换言之,显示对话框、调用注册表接口,甚至通过ADO访问数据库都不成问题。
如果您仔细阅读相关的WTL代码,会发现它使用了“deprecated”的那些ATL 3.1代码,包括CComModule。这是因为WTL7需要兼容VC6,而VC6中并不包含ATL 7。
自然,这样做照顾了以前版本的VC用户,然而,笔者并不赞成如此编写代码。ATL 7的设计中,将不同的Module模板分开的设计显然要比ATL 3.1更为科学,因为缩减模板规模则有助于使代码的最终编译结果更小。
我个人很想把WTL 7中的这些代码修改为与ATL 7规范相适应。然而,一方面由于许可协议的限制,我不能够公开发布修正过的版本;另一方面,我本人现在也没有充裕的时间来做这件事情,因此,这一工作只能期待由Microsoft的开发人员来完成了。但需要强调的是,对于VC7的用户来说,使用ATL 7的相关使用规范要更好一些。
第4章 窗口中的消息处理
Windows系统中,消息机制是非常重要的一环。Windows程序的正确执行离不开消息处理。在WTL/ATL中,在atlwin.h中定义的一组宏能够简化这部分的编写过程。
限于篇幅,这里只介绍少数我们需要自己手工修改,或经常用到的宏。
4.1 COMMAND_ID_HANDLER
这个宏能够定义消息映射项(消息映射项由BEGIN_MSG_MAP和END_MSG_MAP包围)。此消息映射能够处理发到id的菜单、控件或加速键的WM_COMMAND消息,其格式如下:
COMMAND_ID_HANDLER( id, func )
其中,func必须是一个按照如下规格定义的函数:
LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
这是一个非常一般的消息处理函数,如本文开始时所说的那样,我假定读者了解如何撰写这样的函数,并了解各个参数的意义(在后文我会通过实例展示一部分用法,但不打算详细介绍);当然,WTL向导会生成一部分消息处理函数,例如菜单的处理函数,等等。
4.2 CHAIN_MSG_MAP
这个宏用于在消息映射中引用其他类(包括由模板实例化而来的那些)的消息映射。多数编程人员可能会希望这类引用出现在消息映射的最后——这意味着你的代码能够有机会在基类(模板)的消息映射之前处理消息。
典型的WTL程序中的框架窗口会至少包括如下的消息映射引用:
CHAIN_MSG_MAP(CUpdateUI<用户窗口类名>)
CHAIN_MSG_MAP(CFrameWindowImpl<用户窗口类名>)
这两个模板(CUpdateUI和CFrameWindowImpl)分别给出了对于菜单、窗口显示所必需的一些消息处理的支持。这部分代码可以在WTL的atlframe.h中找到。
4.3 通知的处理
通知(Notify)在其他Windows应用程序里面是比较重要的一类消息。由于我们打算使用HTML界面,因此这部分就不那么重要了(我宁可用网页来完成这些任务)。为了便于阅读,在此我把同志处理函数的一般格式列出:
LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
4.4 MESSAGE_HANDLER
尽管这个宏我们可能在绝大多数时候都用不着,但我认为还是有必要说说它。
默认的使用这个宏来进行的消息映射只有1个,那就是对WM_CREATE的处理(当然,在WTL内部还有一些其他的映射,但在这里我并不打算介绍):
MESSAGE_HANDLER(WM_CREATE, OnCreate)
消息处理函数的一般格式
LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
这事儿并不难理解。唯一需要注意的是,MESSAGE_HANDLER是一个“什么都管”的东西,只要是发来这个消息,它就能截获,而不管消息所作用的对象的ID。这在某些时候(例如,希望自己处理多个对象的WM_COMMAND消息)会比较方便。
嗯……现在你可以让自己的程序说点什么了。
4.5 尝试一下使用消息映射
在资源文件中找到菜单,创建一个资源符号ID_FILE_HELLO,并创建一个菜单项,使用这个符。
在消息映射中的某个位置(当然不是在CHAIN_MSG_MAP之后:)添加下面一行:
COMMAND_ID_HANDLER(ID_FILE_HELLO, OnHello)
随后,在这个类中合适的位置建立一个函数(考虑到Hello world确实很俗,咱们换一个):
LRESULT OnHello(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
::MessageBox(NULL, \
_T("You born an excellent WTL programmer!"), \
_T("Congratulations!"), \
0);
return 0;
}
ok,现在您已经了解了如何在一个WTL窗口中加入菜单资源,以及为它添加消息处理函数。值得一提的是,Visual C++ 7.0能够识别WTL工程,并使用向导来帮您完成部分工作。具体操作和MFC类似,这里我就不作介绍了。
4.6 使用HTML界面时的OnCreate函数
本文的标题是,使用WTL来编写HTML用户界面的应用程序。不太严谨地说,只要能够正确地完成消息映射的处理函数,就已经足够使用WTL来写程序了。
在我们最初创建的那个WTL工程中,自动生成了一个叫做CWebUIView的类。它的一个实例出现在我们的Frame Window中:
CWebUIView m_view;
在WTL向导生成的代码中,这是一个public成员。我个人倾向于把它改为protected甚至private成员,当然,通常默认的设定也不会造成太大的问题。
默认生成的代码将在程序开始执行是把你带到www.microsoft.com 。这一点是由OnCreate函数决定的。
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
m_hWndClient = m_view.Create(m_hWnd,
rcDefault,
_T("http://www.microsoft.com"),
WS_CHILD | WS_VISIBLE |
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL |
WS_VSCROLL, WS_EX_CLIENTEDGE);
// 注册用于消息过滤和空闲时窗口刷新的对象
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT(pLoop != NULL);
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
return 0;
}
通常情况下不需要修改这段代码——除了用红色标记出的那个网址。当然,如果您希望对于每一个窗口实例(而不是应用程序实例)进行某种特别的初始化的话另当别论……
?2001-2005 版权归放飞技术小组所有。放飞技术小组保留对此的一切权利。
?2001-2005 Frontfree Workgroup. All rights reserved.全部文章