用WTL构建HTML界面应用程序--微尘世界

来源:百度文库 编辑:神马文学网 时间:2024/04/23 20:49:51
用WTL构建HTML界面应用程序- -
主要针对有一定Windows编程经验的读者。阅读本文需要对COM机制有一定的了解,最好掌握C++的STL,熟悉模板机制。本文假定您使用的是Visual C++(6.0以上版本),并正确安装了WTL 7.0和完整的ATL(Visual C++组件),如果您没有安装Visual C++,请跳过本文;如果您没有安装WTL 7.0,请看这里。
第1章 WTL介绍
ATL想必大家都不陌生,用VC写COM或COM+组件,ATL能够帮助我们完成不少工作。我个人比较喜欢ATL的运作方式?D?D实际上,除了初学COM时编写的几个示例组件之外,我几乎没有完全手工地起草过COM的C++源代码?D?D因为ATL已经完成了这些工作。
如果您不熟悉ATL的话,在这儿我先简单地介绍一下它。ATL是一组C++模板库,最初设计它的目的为编程人员提供一个快捷的途径来编写快而小的COM组件。早期,它特别为实现多层次企业解决方案中的商业逻辑和数据访问的自动化组件而设计。最初的ATL版本中不包括对于界面的支持,但在2.0中,ATL开始包括那些用来支持界面控件的机制。
与MFC相比,ATL最大的优势就在于它是使用模板,而不是类继承实现的。当然它也有不足之处,即它不像MFC那样容易编写界面。通常的应用中,ATL被用来编写非界面的组件,例如,OLE DB、安全验证组件,等等。
对于我本人来说,ATL的优势是非常明显的。MFC需要一些额外的消耗,因为MFC需要支持界面的种种操作,需要支持许多我并不需要的特性(至少是所写的程序中不需要),而由于采用的是类继承,这些额外的设施将不得不被包括到最终的代码中。静态连接MFC库需要增加数百KB,而动态连接,尽管能够有效地减小程序体积,但作为换汤不换药的机制,仍然需要消耗比较多的内存。通常对于组件而言,希望额外的东西越少越好?D?D如果我不需要在屏幕上显示窗口,那么窗口支持就是多余的;如果我不需要处理鼠标事件,那么这段代码就是多余的;而多余的代码最好干脆不要出现。典型的网站服务中,更少的内存占用无疑意味着更大的承载能力。
对于那些希望有良好界面的应用程序开发人员来说,ATL的上述优势的吸引力也许并不大。然而,ATL的许多特性,特别是允许编写更小、更快的代码(与MFC相比),以及对于COM的与生俱来的支持,促使Microsoft在ATL中加入了对编写普通Windows程序的支持。
按照Microsoft的说法,WTL是使用了这些支持的对ATL的一组扩展。它包括了一组扩展ATL的类,使它能够支持更为复杂的,包括应用程序和各种用户界面组件,同时保持ATL最大的好处?D?D能够编写小而快的代码。WTL的目标是成为最好和最简单的实现基于Win32和ATL的应用程序、服务器和控件的方法。
尽管WTL现在还不能像MFC那样方便地用来编写应用程序,但它已经足以在许多地方替代MFC,当然,使用它的开发者也就同时得到了它所带来的好处?D?D紧凑而高效的代码。随着基于HTML页面制作程序界面这一新兴潮流的兴起,使用WTL将不再是一种“另类”的做法。
在本系列的文章中,我将一步一步地带领大家实现一个采用HTML页面作为界面的应用程序。如果你是一个网页设计高手的话,那么你将很快地发现制作一个漂亮的Windows应用程序是如此的得心应手;即使您原来非常熟悉Windows应用程序的界面设计,您也会发现,使用HTML来设计程序界面是一个不错的主意?D?D它将极大地缩短界面的设计时间,并且,与您处于同一个开发组的其他设计人员将能够更容易地帮助您做好界面设计工作,因为编写HTML界面不需要特别的程序设计经验。
第1章 WTL入门
不熟悉COM、ATL都没有关系,因为WTL是一件容易学会的工具。下载之后,执行自解压程序,它将会把WTL复制到你的硬盘上的某个位置。随后,执行与VC版本对应的AppWizard安装程序(一个js文件),并在VC环境中适当地配置Include文件的位置就可以开始使用WTL了。
在开始写程序之前,我还需要唠叨一些关于WTL中宏定义。与MFC一样,ATL/WTL也使用了宏。我并不打算介绍全部这些宏,但几个常用的无疑能够帮助你理解WTL向导生成的代码:
宏名称
描述
ALT_MSG_MAP
标记新的ATL消息映射的开始
ATLASSERT
ATLASSERT宏将执行与C运行环境库中_ASSERTE宏同样的功能
ATLTRACE, ATLTRACE2
在诸如调试窗口的输出设备中显示信息,具体显示什么与调试状态和级别有关。ATLTRACE2是目前推荐的宏,而ATLTRACE主要是为了兼容以前的代码
BEGIN_MSG_MAP
标记默认消息映射的开始
CHAIN_MSG_MAP, CHAIN_MSG_MAP_ALT, CHAIN_MSG_MAP_ALT_MEMBER, CHAIN_MSG_MAP_DYNAMIC, CHAIN_MSG_MAP_MEMBER, COMMAND_HANDLER, MESSAGE_HANDLER, MESSAGE_RANGE_HANDLER
定义一个消息映射项目
DECLARE_WND_CLASS
指定新窗口类的名字。通常在ATL ActiveX控件的控制类中使用
END_MSG_MAP
标记消息映射的结束
WM_FORWARDMSG
将消息转发到别的窗口
以及几个智能指针类,以及我们可能会用到的ATL类。智能指针类可以帮助你更好地撰写程序,并避免内存泄漏等问题。

描述
CAtlWinModule
(ATL 7)为所有需要窗口特性的ATL提供支持的类
CAtlExeModuleT
包括用于创建EXE文件所需要的代码的类
CAtlFile
对Windows文件API进行的封装的类
CAtlFileMapping
对Windows内存映射文件API进行封装的类(限于篇幅,我不打算对这个类的内部构造进行详细介绍)
CAutoPtr
普通的智能指针类
CAutoPtrArray
用于建立智能指针数组
CAutoPtrList
用于建立智能指针表
CAutoVectorPtr
用于建立智能指针向量(类似C++ vector)
CAxWindow
管理作为ActiveX控件宿主窗口的类
CComBSTR
对于BSTR(类似Basic字符串,限于篇幅将不作详细介绍)的封装
CComPtr
管理COM接口指针的智能指针类
CComQIPtr
同上。具体区别将在后文介绍
CComVariant
对于VARIANT类型的封装
CWindow
管理窗口
CWindowImpl
创建和继承窗口
限于篇幅,我不打算介绍WTL的类?D?D除非在后面的文字中提到它们。这里列出它们的名字,WTL的类包括CAppModule,CServerAppModule,CMessageLoop,CMessageFilter,CIdleHandler,CFrameWindowImplBase,CFrameWindowImpl,COwnerDraw,CDialogResize,CMDIWindow,CMDIFrameWindowImpl,CMDIChildWindowImpl,CUpdateUIBase,CUpdateUI,CStatic,CButton,CListBox,CComboBox,CEdit,CEditCommands,CScrollBar,CImageList,CListViewCtrl,CTreeViewCtrl,CTreeItem,CTreeViewCtrlEx,CHeaderCtrl,CToolBarCtrl,CStatusBarCtrl,CTabCtrl,CToolTipCtrl,CToolInfo,CTrackBarCtrl,CUpDownCtrl,CProgressBarCtrl,CHotKeyCtrl,CAnimateCtrl,CRichEditCtrl,CRichEditCommands,CDragListBox,CDragListNotifyImpl,CReBarCtrl,CComboBoxEx,CDateTimePickerCtrl,CFlatScrollBarImpl,CFlatScrollBar,CIPAddressCtrl,CMonthCalendarCtrl,CCustomDraw,CPropertySheetWindow,CPropertySheetImpl,CPropertySheet,CPropertyPageWindow,CPropertyPageImpl,CPropertyPage,CAxPropertyPageImpl,CAxPropertyPage,CFileDialogImpl,CFileDialog,CFolderDialogImpl,CFolderDialog,CFontDialogImpl,CFontDialog,CRichEditFontDialogImpl,CRichEditFontDialog,CColorDialogImpl,CColorDialog,CPrintDialogImpl,CPrintDialog,CPrintDialogExImpl,CPrintDialogEx,CPageSetupDialogImpl,CPageSetupDialog,CFindReplaceDialogImpl,CFindReplaceDialog,CMenu,CDC,CPaintDC,CClientDC,CWindowDC,CPen,CBrush,CFont,CBitmap,CPalette,CRgn,CCommandBarCtrlImpl,CCommandBarCtrl,CBitmapButtonImpl,CBitmapButton,CCheckListViewCtrlImpl,CCheckListViewCtrl,CHyperLinkImpl,CHyperLink,CWaitCursor,CMultiPaneStatusBarCtrlImpl,CMultiPaneStatusBarCtrl,CPaneContainerImpl,CPaneContainer,CScrollImpl,CScrollWindowImpl,CMapScrollImpl,CMapScrollWindowImpl,CSplitterImpl,CSplitterWindowImpl,CSplitterWindow,CTheme,CThemeImpl,CPrinterInfo,CPrinter,CDevMode,CPrinterDC,CPrintJobInfo,CPrintJob,CPrintPreview,CPrintPreviewWindow,CSize,CPoint,CRect,CString,CWinDataExchange,CRecentDocumentList。许多类对于MFC程序员来说是非常熟悉的,仅从类名我们就可以看出,WTL具有相当强大的界面支持。与MFC不同,WTL并不是一个框架?D?D它不强加任何应用程序模型,并能够适应几乎所有的应用程序模型。WTL基本上是模板库,这不仅意味着它包括了全部源代码,并且,也意味着只需要实例化使用到的那些数据结构和内联函数,从而得到尽可能小的代码。
第2章 使用WTL应用程序向导
Visual C++ 7和Visual C++ 6中的WTL应用程序向导的界面差异比较大(内容相同)。为了创建一个支持HTML界面的应用程序,需要作如下选择:

首先是选择WTL应用程序向导,然后

选择SDI应用程序,最后,

将View窗口的类型选定为HTML Page,并去掉Toolbar, Rebar, Command Bar, Status Bar这些特性。
假定我们在第一步中输入的工程名称为webui,则我们将拿到一组文件:stdafx.cpp、stdafx.h(这个就不用说了吧?),webui.cpp(程序实现),MainFrm.h(CMainFrame类),webuiView.h(CWebuiView类),AboutDlg.h(CAboutDlg类),resource.h(资源定义),以及一组资源文件。
现在程序已经可以运行了,其界面类似下图:

其中,白色部分将出现Microsoft的主页,如果连着网的话。
第3章 WTL向导生成执行初启过程
前文已经提到,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
这个宏用于在消息映射中引用其他类(包括由模板实例化而来的那些)的消息映射。多数编程人员可能会希望这类引用出现在消息映射的最后?D?D这意味着你的代码能够有机会在基类(模板)的消息映射之前处理消息。
典型的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;
}
通常情况下不需要修改这段代码?D?D除了用红色标记出的那个网址。当然,如果您希望对于每一个窗口实例(而不是应用程序实例)进行某种特别的初始化的话另当别论……
- 作者:微尘 2003年08月21日, 星期四 03:52
你可以使用这个链接引用该篇日志 http://publishblog.blogdriver.com/blog/tb.b?diaryID=806
_xyz