ATLWTL第六部分.docx
《ATLWTL第六部分.docx》由会员分享,可在线阅读,更多相关《ATLWTL第六部分.docx(19页珍藏版)》请在冰豆网上搜索。
ATLWTL第六部分
第六部分-掌控ActiveX控件
∙下载示例工程-63.2KB
内容
∙简介
∙以AppWizard开始
∙创建工程
∙生成的代码
∙使用资源编辑器添加控件
∙用于掌控控件的ATL类
∙CAxDialogImpl
∙AtlAxWin和CAxWindow
∙调用控件的方法
∙接收控件激发的事件
∙在VC6里添加处理器
∙在VC7里添加处理器
∙事件的知会
∙VC6里的知会
∙VC7里的知会
∙示例工程概述
∙运行时创建ActiveX控件
∙键盘处理
∙下一步
∙修订历史
简介
在这第六部分里,我将介绍ATL对在对话框中掌控(hosting)ActiveX控件的支持。
由于ActiveX控件是ATL的专项,所以这儿并没有相关的WTL类。
不过,因为ATL掌控控件的方式与MFC迥异,所以这是我们要介绍的一个重要主题。
我会介绍如何掌控控件以及接收(sink)事件,并开发一个相比用MFC的ClassWizard写就的应用毫无功能损失的应用程序。
当然,你可以在你写的WTL应用中使用ATL对控件掌控的支持。
本文的示例工程演示了如何掌控IE的Web浏览器控件。
我选择浏览器控件是基于以下两个不错的理由:
1.每个人的机器上都有它,而且
2.它有很多方法并会激发(fire)很多事件,因此用于演示目的,它是确是个很好的控件。
我肯定比不上那些花了很多时间使用IE的Web浏览器控件编写定制浏览器的人们。
但是,通读本文之后,你就会有足够的知识开始编写你自己的定制浏览器了!
以AppWizard开始
创建工程
WTL的AppWizard可以为我们创建马上就能掌控ActiveX控件的应用。
下面我们要创建一个称为 IEHoster 的新工程。
像上一章一样,我们要使用一个非模态对话框,只不过这次要把 EnableActiveXControlHosting 复选框选中,就象这样:
选中这个复选框会使得我们的主对话框从 CAxDialogImpl 中派生,因此能够掌控ActiveX控件。
在VC6的向导里,在第二页上还有另外一个复选框,其文字为 HostActiveXControls,但是选中它对结果代码没有任何影响,所以在第一页里就可以点击Finish按钮完成了。
生成的代码
在这一节里,我会先介绍一些原来没有见过的由AppWizard生成的代码片断;下一节里,我再详细介绍ActiveX掌控类。
第一个需要检视的文件是stdafx.h,其中的包含文件有:
#include
#include
externCAppModule_Module;
#include
#include
#include
#include
//..otherWTLheaders...
atlcom.h和atlhost.h相对重要。
它们包括了一些COM相关的类(比如智能指针 CComPtr),以及用来掌控控件的窗口类。
接下来,再看maindlg.h中 CMainDlg 的声明:
classCMainDlg:
publicCAxDialogImpl,
publicCUpdateUI,
publicCMessageFilter,publicCIdleHandler
CMainDlg 现在是派生于 CAxDialogImpl,后者是使对话框能够掌控ActiveX控件的第一步。
最后,是 WinMain() 中的一行新代码:
intWINAPI_tWinMain(...)
{
//...
_Module.Init(NULL,hInstance);
AtlAxWinInit();
intnRet=Run(lpstrCmdLine,nCmdShow);
_Module.Term();
returnnRet;
}
AtlAxWinInit() 注册了一个名为 AtlAxWin 的窗口类。
该类在ATL为ActiveX控件创建宿主窗口时使用。
由于ATL7的一个改动,你必须给 _Module.Init() 传递一个LIBID。
论坛中的一些人建议在VC7中使用如下代码:
_Module.Init(NULL,hInstance,&LIBID_ATLLib);
这个改动在我这儿工作的很好。
使用资源编辑器添加控件
ATL允许你象在MFC应用中一样使用资源编辑器向对话框上添加ActiveX。
首先,在对话框编辑器中右击,选择 InsertActiveXcontrol:
VC会显示一个你的系统上所安装的控件的列表。
向下滚动到 MicrosoftWebBrowser 并点击 OK,可以将该控件插入到对话框中。
查看一下新控件的属性并将其ID设置为IDC_IE。
对话框看起来应该象下面这样,在编辑器中控件也是可见的:
如果你现在就编译并运行这个应用,你就可以在对话框中看到Web浏览器控件。
由于我们还没有告诉它应该导航到何处,所以它显示的是一个空白页。
在下一节里,我将介绍有关创建和掌控ActiveX控件的ATL类,然后我们再看如果使用这些类来和浏览器进行通讯。
用于掌控控件的ATL类
在对话框中掌控一个ActiveX控件的时候,会有两个类协同工作:
CAxDialogImpl 和 CAxWindow。
它们处理控件容器必须实现的所有接口,并为常见的操作(比如对COM控件查询一个特定的接口)提供一些辅助函数。
CAxDialogImpl
第一个要介绍的就是 CAxDialogImpl。
在你写对话框类的时候,你应该从 CAxDialogImpl 而不是 CDialogImpl 派生,这样才能掌控控件。
CAxDialogImpl 覆盖了 Create() 和 DoModal(),这两个函数由全局函数 AtlAxCreateDialog() 和 AtlAxDialogBox() 分别调用。
因为IEHost对话框是由 Create() 创建的,所以我们应该仔细打量一下 AtlAxCreateDialog()。
AtlAxCreateDialog() 先加载对话框资源,并使用辅助类 _DialogSplitHelper 遍历所有的控件,以寻找那些由资源编辑器生成的并标明是一个需要创建的ActiveX控件的项。
例如,下面是IEHost.rc文件中为Web浏览器生成的项:
CONTROL"",IDC_IE,"{8856F961-340A-11D0-A96B-00C04FD705A2}",
WS_TABSTOP,7,7,116,85
第一个参数是窗口标题(一个空串),第二个是控件ID,第三个是窗口类名。
_DialogSplitHelper:
:
SplitDialogTemplate() 一看到窗口类是由'{' 开头就知道这是一个ActiveX控件项,它会在内存中创建一个新的对话框模板,在新模板里,那些特殊的 CONTROL 项由创建 AtlAxWin窗口的项所替代。
内存中的新项相当于如下定义:
CONTROL"{8856F961-340A-11D0-A96B-00C04FD705A2}",
IDC_IE,"AtlAxWin",WS_TABSTOP,7,7,116,85
其结果是一个 AtlAxWin 窗口会使用相同的ID被创建出来,而且其窗口标题就是ActiveX控件的GUID。
因此,如果你调用GetDlgItem(IDC_IE),则返回的 HWND 值是 AtlAxWin 窗口的,而不是ActiveX控件自己的。
一旦 SplitDialogTemplate() 返回,AtlAxCreateDialog() 再调用 CreateDialogIndirectParam() 以使用修改过的模板来创建对话框。
AtlAxWin和CAxWindow
正如上述指出的,AtlAxWin 是用来作为一个ActiveX控件的容器窗口的。
随 AtlAxWin 使用的还有一个特殊的窗口接口类,名叫 CAxWindow。
当从一个对话框模板中创建 AtlAxWin 时,AtlAxWin 的窗口过程,即 AtlAxWindowProc(),会处理 WM_CREATE 并在消息的响应中创建ActiveX控件。
也可以在运行时创建ActiveX控件而不必在对话框模板中,不过我们在后面才会介绍。
WM_CREATE 处理器会调用全局的 AtlAxCreateControl(),将 AtlAxWin 的窗口标题传递给它,我们知道,窗口标题已经被设置成了Web浏览器的GUID。
AtlAxCreateControl() 再调用更多的函数,但最后代码会到达 CreateNormalizedObject() 处,它会把窗口标题转换为GUID并最终调用 CoCreateInstance() 来创建ActiveX控件。
因为ActiveX控件是 AtlAxWin 的一个子窗口,所以对话框就不能直接访问控件了。
但是,CAxWindow 具有与控件通讯的方法。
最常用的方法之一是 QueryControl(),它又会调用到控件的 QueryInterface()。
比方说,你可以使用 QueryControl() 来从Web浏览器控件中得到一个IWebBrowser2 接口,并使用该接口把浏览器导航到某个URL。
调用控件的方法
现在,我们的对话框里就有一个Web浏览器了,我们可以使用它的COM接口来和它交互。
我们要做的第一件事情是使用它的IWebBrowser2 接口导航到一个新的URL。
在 OnInitDialog() 处理器里,我们可以把掌控着浏览器的 AtlAxWin 附着到一个 CAxWindow 变量上。
CAxWindowwndIE=GetDlgItem(IDC_IE);
接下来,我们声明一个 IWebBrowser2 接口指针并使用 CAxWindow:
:
QueryControl() 向浏览器控件查询该接口:
CComPtrpWB2;
HRESULThr;
hr=wndIE.QueryControl(&pWB2);
QueryControl() 调用Web浏览器的 QueryInterface(),如果成功的话,IWebBrowser2 就会返回给我们。
然后我们可以调用 Navigate():
if(pWB2)
{
CComVariantv;//emptyvariant
pWB2->Navigate(CComBSTR("
&v,&v,&v,&v);
}
接收控件激发的事件
从Web浏览器获取一个接口是相当简单的,而且这还可以允许我们从一个方向-即向控件进行通讯,还有很多的通讯,是以事件的形式从控件而来。
ATL中具有封装了连接点和事件接收的类,使得我们可以接收到由浏览器激发的事件。
要使用这一支持,我们要做四件事:
1.将 IDispEventSimpleImpl 添加到 CMainDlg 的继承列表
2.写一个事件接收映射以表明我们要处理哪些事件
3.为这些事件编写处理器
4.把控件连接到接收映射上(这一过程称为知会(advising))(译注:
对于advise/advising在COM方面的使用,业界尚没有一个被广泛接受的统一译法,此处译者姑且译为知会,之所以没有译为通知,是因为在文中很难与notify/notification区别开来)
VC的IDE在此过程中可以提供极大的帮助-它会为你对 CMainDlg 进行改动,还可以查询ActiveX控件的类型库,显示控件可以激发的事件的列表。
由于VC6和VC7中添加处理器的用户界面不同,下面我分开来介绍。
在VC6里添加处理器
有两种方法可以调出添加处理器的界面:
1.在ClassView窗格里,右击 CMainDlg 并选择菜单中的 AddWindowsMessageHandler。
2.在查看 CMainDlg 的代码时,或者在资源编辑器中查看相关的对话框时,点击WizardBar上Action按钮的下拉箭头,并选择菜单中的 AddWindowsMessageHandler。
选择该命令之后,VC会显示一个对话框,其中有一个题为 classorobjecttohandle 的控件列表。
在列表中选中 IDC_IE,则VC会把WebBrowser控件可以激发的事件填充到 NewWindowsmessage/events 列表中。
因为我们要为DownloadBegin事件添加处理器,所以要选中该事件并点击 AddandEdit 按钮。
VC就会提示你要求给出方法名:
在你第一次添加事件处理器时,VC会对 CMainDlg 做一点改动,使其可以成为一个事件接收器。
头文件中的改动有点零散,汇总起来就是下面的这些代码:
#import"C:
\WINNT\System32\shdocvw.dll"
classCMainDlg:
publicCAxDialogImpl,
publicCUpdateUI,
publicCMessageFilter,publicCIdleHandler,
publicIDispEventImpl
{
//...
public:
BEGIN_SINK_MAP(CMainDlg)
SINK_ENTRY(IDC_IE,0x6a,OnDownloadBegin)
END_SINK_MAP()
void__stdcallOnDownloadBegin()
{
//TODO:
AddCodeforeventhandler.
}
};
#import 语句是一个编译器指令,用以读取 shdocvw.dll (WebBrowserActiveX控件的实现就在此文件中)中的类型库,并为能使用控件中的组件类和接口创建封装类。
通常你会把此指令放到 stdafx.h 中,不过在本例中,我们其实根本不需要它,因为PlatformSDK中已经有了含有WebBrowser的接口和方法的头文件了。
继承列表中现在已经有了 IDispEventImpl。
它有两个模板参数,第一个是我们指派给ActiveX控件的ID,即IDC_IE,第二个是派生于IDispEventImpl 的类的名字。
接收映射由 BEGIN_SINK_MAP 和 END_SINK_MAP 宏隔起来。
每一个 SINK_ENTRY 宏列出了一个 CMainDlg 要处理的事件,宏的参数分别为控件ID(又是 IDC_IE),事件的分派ID,以及事件到达时要调用的函数的名字。
VC会从ActiveX控件的类型库中读取分派ID,所以不必担心应该指定什么数值(exdispid.h 头文件中列出了IE和资源浏览器发送的许多事件的ID,如果你到其中查看,你可以看到0x6A对应着常量DISP_DOWNLOADBEGIN)。
最后面是一个新方法,OnDownloadBegin()。
对那些有参数的事件,VC会为方法设置正确的原型。
所有的事件处理器都是 __stdcall 调用规范,因为它们是COM方法。
在VC7里添加处理器
也有两种方法可以添加事件处理器。
你可以在对话框编辑器中的ActiveX控件上右击,并在菜单上选择 AddEventHandler。
你可以在显示出的对话框里选择事件名并设置处理器的名字。
点击 AddandEdit 按钮将添加该处理器,对 CMainDlg 做必要的更改,并打开 maindlg.cpp 文件,高亮显示着新添加的处理器。
另一个方法是查看 CMainDlg 的属性页,展开 Controls 结点,然后是 IDC_IE 结点。
在 IDC_IE 结点下,你可以找到控件激发的事件。
你可以点击事件名边上的箭头,选择菜单上的 [MethodName] 来添加处理器。
你还可以稍后修改处理器的名字,当然还是在属性页里改。
VC7对 CMainDlg 的修改和VC6几乎一样,一个例外是并不添加 #import 指令。
事件的知会
最后一步是知会到控件,CMainDlg 想要接收由WebBrowser控件激发的事件。
此过程在VC6和VC7里仍然不一样,所以还需要分别介绍。
相同的是,知会都发生在 OnInitDialog() 里,反知会(unadvising)发生在 OnDestroy() 里。
VC6中的知会
VC6的ATL里有一个全局函数 AtlAdviseSinkMap()。
该函数接收一个具有接收映射的C++对象的指针(通常为this指针),以及一个布尔值。
如果布尔值为true,则对象是希望开始接收事件,如果为false,则对象希望停止接收事件。
AtlAdviseSinkMap() 知会对话框中所有的控件开始或者停止向C++对象发送事件。
要使用此函数,就要为 WM_INITDIALOG 和 WM_DESTROY 添加处理器,然后再像这样调用 AtlAdviseSinkMap():
BOOLCMainDlg:
:
OnInitDialog(...)
{
//Beginsinkingevents
AtlAdviseSinkMap(this,true);
}
voidCMainDlg:
:
OnDestroy()
{
//Stopsinkingevents
AtlAdviseSinkMap(this,false);
}
AtlAdviseSinkMap() 返回一个 HRESULT 表示知会成功与否。
如果 AtlAdviseSinkMap() 在 中失败了,那么你就不能从有的(或者是全部的)ActiveX控件处得到事件。
VC7中的知会
在VC7里,CAxDialogImpl 有一个名为 AdviseSinkMap() 的方法封装了 AtlAdviseSinkMap()。
AdviseSinkMap() 接收一个布尔参数,其意义与AtlAdviseSinkMap() 的第二个参数相同。
AdviseSinkMap() 检查到类里有一个接收映射,就会调用 AtlAdviseSinkMap()。
相对于VC6,最大的不同在于 CAxDialogImpl() 已经有了为你调用 AdviseSinkMap() 的 WM_INITDIALOG 和 WM_DESTROY 的处理器。
要想受益于此特性,就要在 CMainDlg 消息映射的开头添加一个 CHAIN_MSG_MAP 宏,就像这样:
BEGIN_MSG_MAP(CMainDlg)
CHAIN_MSG_MAP(CAxDialogImpl)
//restofthemessagemap...
END_MSG_MAP()
示例工程概述
我们已经知道了事件接收是怎么工作的,现在我们来看看整个IEHost工程。
正像我们所讨论的,它掌控了Web浏览器控件,并处理了六个事件。
它还显示了一个事件的列表,这样你就可以知道定制浏览器是怎样使用这些事件来在UI上提供进度的。
应用处理的事件有:
∙BeforeNavigate2 和 NavigateComplete2:
这两个事件可以使应用监测到URL导航。
如果你愿意,你可以在 BeforeNavigate2 的响应里取消导航。
∙DownloadBegin 和 DownloadComplete:
应用程序使用这两个事件来控制表示浏览器正在工作的“等待”信息。
更精致的程序还会像IE一样使用个动画。
∙CommandStateChange:
此事件告诉应用什么时候可以使用“后退”和“前进”导航命令。
应用会据此相应地启用或者禁止后退和前进按钮。
∙StatusTextChange:
好几种情况下都会激发此事件,例如当鼠标光标移动到超链接上时。
此事件会发送一个字符串,应用会响应此事件,将字符串显示到浏览器窗口下面的一个静态控件里。
应用里还有四个控制浏览器的按钮:
后退、前进、停止以及重新加载。
这些按钮会调用到相应的 IWebBrowser2 方法。
事件以及伴随事件的数据都被记录到了列表控件里,所以事件一激发你就能看到。
你可以关闭任一事件的日志,这样你就可以只观测其中的一两个。
为了演示一些实质性的事件处理,在 BeforeNavigate2 处理器中会检查URL,如果其中包含了“”,则本次导航会被取消。
作为IE的插件而不是HTTP代理运行的广告和弹出窗口拦截器使用的正是这个方法。
下面是作此检查的代码。
void__stdcallCMainDlg:
:
OnBeforeNavigate2(
IDispatch*pDisp,VARIANT*URL,VARIANT*Flags,
VARIANT*TargetFrameName,VARIANT*PostData,
VARIANT*Headers,VARIANT_BOOL*Cancel)
{
CStringsURL=URL->bstrVal;
//Youcanset*CanceltoVARIANT_TRUEtostopthe
//navigationfromhappening.Forexample,tostop
//navigatestoeviltrackingcompanieslike:
if(sURL.Find(_T(""))>0)
*Cancel=VARIANT_TRUE;
}
下面是我们的应用在浏览论坛时的样子:
IEHost还演示了另外好几个在前文中介绍过的WTL特性:
CBitmapButton(用于浏览器控制按钮),CListViewCtrl(用于事件记录),DDX(用于跟踪复选框的状态),以及 CDialogResize。
运行时创建ActiveX控件
在运行时而不是在资源编辑器中创建ActiveX控件也是可以的。
About对话框演示了这一技术。
对话框资源包含了一个占位用的分组框,表明了浏览器控件该在什么位置:
在 OnInitDialog() 中,我们使用 CAxWindow 来创建一个新的 AtlAxWin,它会与占位控件使用相同的 RECT,而占位控件随即被销毁:
LRESUL