从内嵌的IE控件里访问外部框架的方法
由于IE控件可以很容易的制作出比较漂亮的界面,所以现在的程序都喜欢内嵌一个IE控件。但是,但是如何在IE控件里运行JavaScript脚本,并访问外部框架的方法属性呢?以前,我一直不明白。最近,参考了MSDN的例子,发现其实不是很困难嘛。现在把自己的一些体会与各位分享,不当之处还请多多见谅。
我们知道在OLE的世界里,有一个Container的概念,而所谓的IE控件就是生存在这个Container里的。通过一套标准接口,控件和Container可以连接起来,其中一个就是IDocHostUIHandler接口。Container必须实现这个接口,而且创建内部的控件时,把该接口暴露给控件。控件通过这个接口,可以放问到外部Container的很多信息,包括是否显示右键菜单、响应快捷键等等。
而现在对我们来说,最重要的一个方法就是GetExternal。该方法用于获得一个外部的IDispatch接口! 有了这个IDispatch接口,还有什么不能做的?剩下的工作就是把整个框架的模型通过这个IDispatch输出!
IDispatch如何实现?
IDispatch接口难以实现吗?一点也不,因为MFC里CDialog , CWnd等类都是从CCmdTarget派生来的,所以已经自动支持IDispatch接口了。你所要做的只是添加方法/属性,并在构造函数里添加上EnableAutomation();
头文件里记得加上:
DECLARE_DISPATCH_MAP()
DECLARE_INTERFACE_MAP()
实现文件里:
BEGIN_DISPATCH_MAP(…)
//{{AFX_DISPATCH_MAP(…)
//……
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
实现对象模型:
根据上面的方法,你仅仅可以把某一个Dialog或Wnd的IDispatch接口传递给IE控件,而如果要传递很多的怎么办呢?是啦,这就是对象模型(Object Model)的概念了。我们需要引出一个顶层的IDispatch接口/对象,然后从这个顶层的IDispatch接口/对象引出整个的对象模型!如果你曾经用过Word等产品的自动化接口的话,你就一定很容易理解了。Object Model是微软产品里自动化的一个关键。
好了,现在就让我们来通过这个IDispatch接口引出整个的Object Model(我们自己定义的)。而且为了不让该Object Model与特定的实现类困死在一起,我们单独创建了一个对象叫做CMyObj,并且从CCmdTarget派生,这样就自动支持IDispatch接口了。并为CMyObj添加方法和属性,而具体的值由实现类进行填充。而在GetExternal的时候,我们把CMyObj的IDispatch接口传递出去。通过访问CMyObj的IDispatch接口的属性/方法,我们就可以访问到实现类了!而且,通过这样的模式你可以暴露出任意的COM对象,而并不一定是内部实现的COM对象!你需要做的,就是得到该COM对象的IDispatch接口。
如何实现自定义的Container?
那么GetExternal在什么地方实现呢?对了GetExternal是属于IDocHostUIHandler的,而IDocHostUIHandler是由Container来实现的。最后,Container在MFC里,是由框架来实现的。所以,你必须重载/覆盖框架的实现才能实现自定义的GetExternal!MSDN例子Driller是这样实现的:
// Set our control containment up but using our control container
// management class instead of MFC's default
CCustomOccManager *pMgr = new CCustomOccManager;
AfxEnableControlContainer(pMgr);
在相关的重载函数里,实现GetExternal,返回我们的CMyObj的IDispatch接口。STDMETHODIMP CCustomControlSite::XDocHostUIHandler::GetExternal(
/* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch)
{
// return the IDispatch we have for extending the object Model
extern CViewClientApp theApp;
IDispatch* pDisp = (IDispatch*)theApp.m_pMyObj->GetIDispatch(true);
*ppDispatch = pDisp;
return S_OK;
}
如何在IE控件里访问框架?
好了,现在我们只要在IE的HTML脚本里写上window.external 就可以访问到我们Object Model了。Do anything you want!
另外,需要提一点的是,我的例子里使用的是基类是CHtmlView,有一点麻烦的地方就是它的Create函数里调用了AfxEnableControlContainer()(注意不带参数),从而会把我们的自定义Container覆盖了。解决的方法是,重载该Create方法,并把原代码拷贝过来,并把AfxEnableControlContainer()给注释掉!这个方法好像有点蠢,呵呵。
终于敲完了上面的文字,好像稀里糊涂的,也不知道各位有没有坚持看完?有没有明白写的什么意思?呵呵,对照源码看吧。哦,源码好像也不怎么清晰,主要是为了写一个工具……: )
参考资料:
Take Total Control of Internet Explorer with Advanced Hosting Interfaces
http://www.microsoft.com/Mind/1098/AdvHost/advhost.htm
MSDN的例子Driller(MFC)