高级定制对话框式应用中的WebBrowser控件。






4.97/5 (52投票s)
演示如何在对话框式应用中自定义Microsoft WebBrowser控件,以允许自定义上下文菜单、消息框、窗口、模态对话框,以及从JavaScript调用C++函数到你的应用程序,使用window.external。
引言
白天我是网页开发人员,晚上我就是C++/MFC程序员!在白天工作中,我们的客户要求我们提供一个解决方案,让他们能够更安全地让员工通过互联网参加考试和查看课程内容,而不是使用普通的IE浏览器,这样他们的员工就不会轻易作弊或复制内容并在eBay上出售。所以我想到,如果我能创建一个全屏的窗口,隐藏任务栏,禁用键盘按键,以及其他我需要做的任何事情来锁定系统,以便在我的应用程序打开时。然而,我需要对Internet Explorer Web Browser控件进行大量的自定义(自定义上下文菜单、对话框、窗口、JavaScript调用C++、消息框,以及覆盖浏览器窗口内的键盘按键)。
我曾想尝试用C#来编写,并在比简单的数据库应用程序更复杂的项目上尝试一些.NET的东西,但很快我发现视频在通过C#实现的HTML页面中根本无法播放(这对于我们创建交互式媒体解决方案来说是个大问题)。更不用说,我想要做的每一个原始Win32 API调用都需要声明一个函数,这真的快把我逼疯了。我甚至尝试了.NET CF库,但它缺少我需要的许多函数。我将我写的一些用于通过系统级钩子拦截键盘按键的C++代码移植到了C#,当我最终全部移植到C#时,它随机崩溃,并且只捕获了我需要的一些按键(C#可以为你进行垃圾回收,但这并不意味着你的应用程序不会崩溃)。不用说,我决定开始用C++/MFC来编写(C++ 6.0 MFC万岁!)。
研究,或者我喜欢说的(漫无目的地在MSDN的深处游荡)
我开始研究如何使用Microsoft WebBrowser
控件创建一个自定义网页浏览器,该浏览器允许我拥有自定义消息框、自定义模态对话框、自定义窗口、自定义上下文菜单,能够从JavaScript调用C++代码,并禁用特定的按键组合。我在MSDN和其他网站上找到了大量的文档,告诉我要实现IDocHostShowUI
和IDocHostUIHandler
COM接口,以及IDispatch
之类的东西。唯一的问题是,我不知道这意味着什么,也不知道如何做。我找到的源代码只完成了我需要做的一部分工作,并且使用的是CHtmlView
,而我需要它在对话框式应用程序中工作。源代码在许多我需要做的其他任务上也让我一头雾雾。
通过一篇我在Microsoft知识库中找到的文章(经过数小时的搜索),然后编写和调整一些代码,我终于能够在我自定义的网页浏览器应用程序中实现我所需的所有功能。
如何创建自定义网页浏览器应用程序
在我们开始之前,我知道这看起来很吓人,因为我们要添加很多头文件和CPP文件,而且这篇文章的篇幅也很长,但它实际上并没有看起来那么复杂或吓人。我认为这一点很重要,因为我知道当我开始阅读那篇知识库文章时,我也差点被吓退了 :)
我必须说,这很大程度上要归功于Microsoft知识库文章 - 236312。它在允许我创建灵活满足我需求的自定义网页浏览器解决方案方面发挥了重要作用。
首先,你需要使用MFC AppWizard创建一个对话框式应用程序。接下来,在Visual Studio的“资源视图”选项卡中,以设计模式打开你的对话框。然后转到“项目”,“添加到项目”,“组件和控件...”。在“已注册的ActiveX控件”文件夹中滚动控件,直到找到Microsoft Web Browser。然后点击它,点击插入按钮,然后关闭“组件和控件库”对话框窗口。
现在,如果你查看对话框式应用程序设计视图中的控件工具栏,你会看到一个新的带有地球图标的项目。这就是我们的网页浏览器控件。所以,只需点击该项目,然后将其放在你的对话框上。现在按(Ctrl+W)调出类向导,为IDC_EXPLORER1
添加一个名为m_browser
的成员变量。
现在,这就是我之前提到的知识库文章发挥作用的地方。我通常会直接引用知识库文章,但我们都知道Microsoft链接的可靠性如何。因此,我将基本复制知识库文章中的说明并粘贴在下面。
- 打开CustomBrowser.h,并按照下面所示,在
CCustomBrowserApp
类中添加一个公共成员变量。public: class CImpIDispatch* m_pDispOM;
- 向项目中添加一个名为Custsite.h的新头文件。将以下代码复制并粘贴到其中。
//=---------------------------------------------------------------= // (C) Copyright 1996-1999 Microsoft Corporation. All Rights Reserved. //=---------------------------------------------------------------= #ifndef __CUSTOMSITEH__ #define __CUSTOMSITEH__ #include "idispimp.h" #include <MSHTMHST.H> // // NOTE: // Some of the code in this file is MFC implementation specific. // Changes in future versions of MFC implementation may require // the code to be changed. Please check the readme of this // sample for more information // class CCustomControlSite:public COleControlSite { public: CCustomControlSite(COleControlContainer *pCnt): COleControlSite(pCnt){} protected: DECLARE_INTERFACE_MAP(); BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler) STDMETHOD(ShowContextMenu)(/* [in] */ DWORD dwID, /* [in] */ POINT __RPC_FAR *ppt, /* [in] */ IUnknown __RPC_FAR *pcmdtReserved, /* [in] */ IDispatch __RPC_FAR *pdispReserved); STDMETHOD(GetHostInfo)( /* [out][in] */ DOCHOSTUIINFO __RPC_FAR *pInfo); STDMETHOD(ShowUI)( /* [in] */ DWORD dwID, /* [in] */ IOleInPlaceActiveObject __RPC_FAR *pActiveObject, /* [in] */ IOleCommandTarget __RPC_FAR *pCommandTarget, /* [in] */ IOleInPlaceFrame __RPC_FAR *pFrame, /* [in] */ IOleInPlaceUIWindow __RPC_FAR *pDoc); STDMETHOD(HideUI)(void); STDMETHOD(UpdateUI)(void); STDMETHOD(EnableModeless)(/* [in] */ BOOL fEnable); STDMETHOD(OnDocWindowActivate)(/* [in] */ BOOL fEnable); STDMETHOD(OnFrameWindowActivate)(/* [in] */ BOOL fEnable); STDMETHOD(ResizeBorder)( /* [in] */ LPCRECT prcBorder, /* [in] */ IOleInPlaceUIWindow __RPC_FAR *pUIWindow, /* [in] */ BOOL fRameWindow); STDMETHOD(TranslateAccelerator)( /* [in] */ LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID); STDMETHOD(GetOptionKeyPath)( /* [out] */ LPOLESTR __RPC_FAR *pchKey, /* [in] */ DWORD dw); STDMETHOD(GetDropTarget)( /* [in] */ IDropTarget __RPC_FAR *pDropTarget, /* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget); STDMETHOD(GetExternal)( /* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch); STDMETHOD(TranslateUrl)( /* [in] */ DWORD dwTranslate, /* [in] */ OLECHAR __RPC_FAR *pchURLIn, /* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut); STDMETHOD(FilterDataObject)( /* [in] */ IDataObject __RPC_FAR *pDO, /* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet); END_INTERFACE_PART(DocHostUIHandler) }; class CCustomOccManager :public COccManager { public: CCustomOccManager(){} COleControlSite* CreateSite(COleControlContainer* pCtrlCont) { CCustomControlSite *pSite = new CCustomControlSite(pCtrlCont); return pSite; } }; #endif
- 添加一个名为Custsite.cpp的新CPP文件,并将以下代码添加到其中。
//=------------------------------------------------------------------= // (C) Copyright 1996-1999 Microsoft Corporation. All Rights Reserved. //=------------------------------------------------------------------= // // NOTE: // Some of the code in this file is MFC implementation specific. // Changes in future versions of MFC implementation may require // the code to be changed. Please check the readme of this // sample for more information // #include "stdafx.h" #undef AFX_DATA #define AFX_DATA AFX_DATA_IMPORT #include "CustomBrowser.h" // NOTE: This line is a hardcoded reference to an MFC header file // this path may need to be changed // to refer to the location of VC5 install // for successful compilation. #include <..\src\occimpl.h> #undef AFX_DATA #define AFX_DATA AFX_DATA_EXPORT #include "custsite.h" BEGIN_INTERFACE_MAP(CCustomControlSite, COleControlSite) INTERFACE_PART(CCustomControlSite, IID_IDocHostUIHandler, DocHostUIHandler) END_INTERFACE_MAP() ULONG FAR EXPORT CCustomControlSite::XDocHostUIHandler::AddRef() { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return pThis->ExternalAddRef(); } ULONG FAR EXPORT CCustomControlSite::XDocHostUIHandler::Release() { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return pThis->ExternalRelease(); } HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::QueryInterface(REFIID riid, void **ppvObj) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) HRESULT hr = (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj); return hr; } // * CImpIDocHostUIHandler::GetHostInfo // * // * Purpose: Called at initialization // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::GetHostInfo( DOCHOSTUIINFO* pInfo ) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER; pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT; return S_OK; } // * CImpIDocHostUIHandler::ShowUI // * // * Purpose: Called when MSHTML.DLL shows its UI // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::ShowUI( DWORD dwID, IOleInPlaceActiveObject * /*pActiveObject*/, IOleCommandTarget * pCommandTarget, IOleInPlaceFrame * /*pFrame*/, IOleInPlaceUIWindow * /*pDoc*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) // We've already got our own // UI in place so just return S_OK return S_OK; } // * CImpIDocHostUIHandler::HideUI // * // * Purpose: Called when MSHTML.DLL hides its UI // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::HideUI(void) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return S_OK; } // * CImpIDocHostUIHandler::UpdateUI // * // * Purpose: Called when MSHTML.DLL updates its UI // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::UpdateUI(void) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) // MFC is pretty good about updating // it's UI in it's Idle loop so I don't do anything here return S_OK; } // * CImpIDocHostUIHandler::EnableModeless // * // * Purpose: Called from MSHTML.DLL's // * IOleInPlaceActiveObject::EnableModeless // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::EnableModeless(BOOL /*fEnable*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } // * CImpIDocHostUIHandler::OnDocWindowActivate // * // * Purpose: Called from MSHTML.DLL's // * IOleInPlaceActiveObject::OnDocWindowActivate // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::OnDocWindowActivate(BOOL /*fActivate*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } // * CImpIDocHostUIHandler::OnFrameWindowActivate // * // * Purpose: Called from MSHTML.DLL's // * IOleInPlaceActiveObject::OnFrameWindowActivate // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::OnFrameWindowActivate(BOOL /*fActivate*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } // * CImpIDocHostUIHandler::ResizeBorder // * // * Purpose: Called from MSHTML.DLL's // * IOleInPlaceActiveObject::ResizeBorder // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::ResizeBorder( LPCRECT /*prcBorder*/, IOleInPlaceUIWindow* /*pUIWindow*/, BOOL /*fRameWindow*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } // * CImpIDocHostUIHandler::ShowContextMenu // * // * Purpose: Called when MSHTML.DLL // * would normally display its context menu // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::ShowContextMenu( DWORD /*dwID*/, POINT* /*pptPosition*/, IUnknown* /*pCommandTarget*/, IDispatch* /*pDispatchObjectHit*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) // We've shown our own context menu. //MSHTML.DLL will no longer try return S_OK; to show its own. } // * CImpIDocHostUIHandler::TranslateAccelerator // * // * Purpose: Called from MSHTML.DLL's TranslateAccelerator routines // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::TranslateAccelerator(LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return S_FALSE; } // * CImpIDocHostUIHandler::GetOptionKeyPath // * // * Purpose: Called by MSHTML.DLL // * to find where the host wishes to store // * its options in the registry // * HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::GetOptionKeyPath(BSTR* pbstrKey, DWORD) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } STDMETHODIMP CCustomControlSite::XDocHostUIHandler::GetDropTarget( /* [in] */ IDropTarget __RPC_FAR *pDropTarget, /* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } STDMETHODIMP CCustomControlSite::XDocHostUIHandler::GetExternal( /* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) { // return the IDispatch we have for extending the object Model IDispatch* pDisp = (IDispatch*)theApp.m_pDispOM; pDisp->AddRef(); *ppDispatch = pDisp; return S_OK; } STDMETHODIMP CCustomControlSite::XDocHostUIHandler::TranslateUrl( /* [in] */ DWORD dwTranslate, /* [in] */ OLECHAR __RPC_FAR *pchURLIn, /* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; } STDMETHODIMP CCustomControlSite::XDocHostUIHandler::FilterDataObject( /* [in] */ IDataObject __RPC_FAR *pDO, /* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return E_NOTIMPL; }
- 添加一个名为Idispimp.h的新头文件,并将以下代码添加到其中。
/* * IDispimp.H * IDispatch * * Copyright (c)1995-1999 Microsoft Corporation, All Rights Reserved */ #ifndef _IDISPIMP_H_ #define _IDISPIMP_H_ class CImpIDispatch : public IDispatch { protected: ULONG m_cRef; public: CImpIDispatch(void); ~CImpIDispatch(void); STDMETHODIMP QueryInterface(REFIID, void **); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); //IDispatch STDMETHODIMP GetTypeInfoCount(UINT* pctinfo); STDMETHODIMP GetTypeInfo(/* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo** ppTInfo); STDMETHODIMP GetIDsOfNames( /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId); STDMETHODIMP Invoke( /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr); }; #endif //_IDISPIMP_H_
- 添加一个名为Idispimp.cpp的新CPP文件,并将以下代码添加到其中。
/* * idispimp.CPP * IDispatch for Extending Dynamic HTML Object Model * * Copyright (c)1995-1999 Microsoft Corporation, All Rights Reserved */ #include "stdafx.h" #include "idispimp.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif // Hardcoded information for extending the Object Model // Typically this would be supplied through a TypeInfo // In this case the name "xxyyzz" maps to DISPID_Extend const WCHAR pszExtend[10]=L"xxyyzz"; #define DISPID_Extend 12345 /* * CImpIDispatch::CImpIDispatch * CImpIDispatch::~CImpIDispatch * * Parameters (Constructor): * pSite PCSite of the site we're in. * pUnkOuter LPUNKNOWN to which we delegate. */ CImpIDispatch::CImpIDispatch( void ) { m_cRef = 0; } CImpIDispatch::~CImpIDispatch( void ) { ASSERT( m_cRef == 0 ); } /* * CImpIDispatch::QueryInterface * CImpIDispatch::AddRef * CImpIDispatch::Release * * Purpose: * IUnknown members for CImpIDispatch object. */ STDMETHODIMP CImpIDispatch::QueryInterface ( REFIID riid, void **ppv ) { *ppv = NULL; if ( IID_IDispatch == riid ) { *ppv = this; } if ( NULL != *ppv ) { ((LPUNKNOWN)*ppv)->AddRef(); return NOERROR; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void) { return ++m_cRef; } STDMETHODIMP_(ULONG) CImpIDispatch::Release(void) { return --m_cRef; } //IDispatch STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT* /*pctinfo*/) { return E_NOTIMPL; } STDMETHODIMP CImpIDispatch::GetTypeInfo(/* [in] */ UINT /*iTInfo*/, /* [in] */ LCID /*lcid*/, /* [out] */ ITypeInfo** /*ppTInfo*/) { return E_NOTIMPL; } STDMETHODIMP CImpIDispatch::GetIDsOfNames( /* [in] */ REFIID riid, /* [size_is][in] */ OLECHAR** rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID* rgDispId) { HRESULT hr; UINT i; // Assume some degree of success hr = NOERROR; // Hardcoded mapping for this sample // A more usual procedure would be to use a TypeInfo for ( i=0; i < cNames; i++) { if ( 2 == CompareString( lcid, NORM_IGNOREWIDTH, (char*)pszExtend, 3, (char*)rgszNames[i], 3 ) ) { rgDispId[i] = DISPID_Extend; } else { // One or more are unknown so // set the return code accordingly hr = ResultFromScode(DISP_E_UNKNOWNNAME); rgDispId[i] = DISPID_UNKNOWN; } } return hr; } STDMETHODIMP CImpIDispatch::Invoke( /* [in] */ DISPID dispIdMember, /* [in] */ REFIID /*riid*/, /* [in] */ LCID /*lcid*/, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS* pDispParams, /* [out] */ VARIANT* pVarResult, /* [out] */ EXCEPINFO* /*pExcepInfo*/, /* [out] */ UINT* puArgErr) { // For this sample we only support // a Property Get on DISPID_Extend // returning a BSTR with "Wibble" as the value if ( dispIdMember == DISPID_Extend ) { if ( wFlags & DISPATCH_PROPERTYGET ) { if ( pVarResult != NULL ) { WCHAR buff[10]=L"Wibble"; BSTR bstrRet = SysAllocString( buff ); VariantInit(pVarResult); V_VT(pVarResult)=VT_BSTR; V_BSTR(pVarResult) = bstrRet; } } } return S_OK; }
- 打开CustomBrowser.cpp,并在
CCustomBrowser
的InitInstance
中添加以下代码。同时注释掉对AfxEnableControlContainer()
的调用。BOOL CCustomBrowserApp::InitInstance() { CCustomOccManager *pMgr = new CCustomOccManager; // Create an IDispatch class for // extending the Dynamic HTML Object Model m_pDispOM = new CImpIDispatch; // Set our control containment up // but using our control container // management class instead of MFC's default AfxEnableControlContainer(pMgr); // AfxEnableControlContainer(); //... rest of the code here }
- 还将以下内容添加到CustomBrowser.cpp的包含文件列表中。
#include "afxpriv.h" #include <..\src\occimpl.h> #include "CustSite.h"
- 转到CustomBrowser.h,并在文件底部添加以下语句。
extern CCustomBrowserApp theApp;
现在我们已经完成了所有工作,这是一个不错的开始,但我们还有很长的路要走。基本上,到目前为止,我们已经实现了IDocHostUIHandler
和IDispatch
接口。这将允许我们做诸如自定义上下文菜单和使用window.external
从JavaScript调用C++函数之类的事情。但是,我们还想实现IDocHostShowUI
接口,以便我们可以提供自己的自定义消息框。我们还需要编写一些额外的代码,以便能够通过我们自定义的网页浏览器打开模态和非模态对话框,创建JavaScript可以调用的C++函数,并显示自定义上下文菜单。
我们将从实现IDocHostShowUI
接口开始。
- 打开Custsite.h文件,并在
public
下,紧挨着CCustomControlSite(COleControlContainer *pCnt):COleControlSite(pCnt){}
添加以下代码。BEGIN_INTERFACE_PART(DocHostShowUI, IDocHostShowUI) INIT_INTERFACE_PART(CDocHostSite, DocHostShowUI) STDMETHOD(ShowHelp)( /* [in ] */ HWND hwnd, /* [in ] */ LPOLESTR pszHelpFile, /* [in ] */ UINT uCommand, /* [in ] */ DWORD dwData, /* [in ] */ POINT ptMouse, /* [out] */ IDispatch __RPC_FAR *pDispatchObjectHit); STDMETHOD(ShowMessage)( /* [in ] */ HWND hwnd, /* [in ] */ LPOLESTR lpstrText, /* [in ] */ LPOLESTR lpstrCaption, /* [in ] */ DWORD dwType, /* [in ] */ LPOLESTR lpstrHelpFile, /* [in ] */ DWORD dwHelpContext, /* [out] */ LRESULT __RPC_FAR *plResult); END_INTERFACE_PART(DocHostShowUI)
- 打开Custsite.cpp文件,并添加下面以粗体显示的以下代码行。
BEGIN_INTERFACE_MAP(CCustomControlSite, COleControlSite) INTERFACE_PART(CCustomControlSite, IID_IDocHostShowUI, DocHostShowUI) INTERFACE_PART(CCustomControlSite, IID_IDocHostUIHandler, DocHostUIHandler) END_INTERFACE_MAP()
- 在上面显示的紧下方添加以下代码。
ULONG CCustomControlSite::XDocHostShowUI::AddRef() { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); return pThis->ExternalAddRef(); } ULONG CCustomControlSite::XDocHostShowUI::Release() { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); return pThis->ExternalRelease(); } HRESULT CCustomControlSite::XDocHostShowUI::QueryInterface (REFIID riid, void ** ppvObj) { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); return pThis->ExternalQueryInterface( &riid, ppvObj ); } HRESULT CCustomControlSite::XDocHostShowUI::ShowHelp( HWND hwnd, LPOLESTR pszHelpFile, UINT nCommand, DWORD dwData, POINT ptMouse, IDispatch * pDispatchObjectHit) { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); return S_OK; } HRESULT CCustomControlSite::XDocHostShowUI::ShowMessage( HWND hwnd, LPOLESTR lpstrText, LPOLESTR lpstrCaption, DWORD dwType, LPOLESTR lpstrHelpFile, DWORD dwHelpContext, LRESULT * plResult) { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); MessageBox(hwnd, (CString)lpstrText, "Custom Browser", dwType); return S_OK; }
现在我们已经完成了所有必需接口的实现,以便在所需范围内自定义浏览器。现在我们只需要编写实际执行自定义的代码,并且我们有一个框架,可以在需要提供需要高级功能的自定义浏览器解决方案时使用。
首先,让我们进入Custsite.cpp文件。在这个文件中,我们可以自定义我们的消息框。换句话说,每次有人从JavaScript调用函数 alert('some text here');
时,他们将看到我们自定义的消息框。找到名为ShowMessage
的函数,并在该函数中,我们将它显示为一个关键错误图像,而不是标准的警告感叹号图标,仅作示例。
HRESULT CCustomControlSite::XDocHostShowUI::ShowMessage(HWND hwnd, LPOLESTR lpstrText, LPOLESTR lpstrCaption, DWORD dwType, LPOLESTR lpstrHelpFile, DWORD dwHelpContext, LRESULT * plResult) { METHOD_PROLOGUE(CCustomControlSite, DocHostShowUI); //now all of our alerts will show the error icon instead of the //warning icon. Of course you could do a lot more here to customize //but this is just to show you for a simple example MessageBox(hwnd, (CString)lpstrText, "Custom Browser", /*dwType*/MB_ICONERROR); return S_OK; }
如果你愿意,你也可以捕获常见的按键组合并取消它们或让它们执行不同的操作。要禁用一些常见的按键组合,请进入Custsite.cpp文件,并找到下面显示的这部分代码。
HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::TranslateAccelerator (LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return S_FALSE; }
用下面显示的以下代码替换上面显示的这部分代码。
HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::TranslateAccelerator(LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) //disable F5 if(lpMsg->message == WM_KEYDOWN && GetAsyncKeyState(VK_F5) < 0) return S_OK; if(GetKeyState(VK_CONTROL) & 0x8000) { //disable ctrl + O if(lpMsg->message == WM_KEYDOWN && GetAsyncKeyState(0x4F) < 0) return S_OK; //disable ctrl + p if(lpMsg->message == WM_KEYDOWN && GetAsyncKeyState(0x50) < 0) return S_OK; //disable ctrl + N if(lpMsg->message == WM_KEYDOWN && GetAsyncKeyState(0x4E) < 0) return S_OK; } //disable back space if(lpMsg->wParam == VK_BACK) return S_OK; return S_FALSE; }
您还可以提供自己的自定义上下文菜单,而不是Internet Explorer的默认菜单,方法是在ShowContextMenu
处理程序中放置代码,如下所示。
HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::ShowContextMenu( DWORD /*dwID*/, POINT* pptPosition, IUnknown* /*pCommandTarget*/, IDispatch* /*pDispatchObjectHit*/) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) //show our custom menu CMenu menu; menu.LoadMenu(IDR_CUSTOM_POPUP); CMenu* pSubMenu = menu.GetSubMenu(0); //Because we passed in theApp.m_pMainWnd all of our //WM_COMMAND handlers for the menu items must be handled //in CCustomBrowserApp. If you want this to be your dialog //you will have to grab a pointer to your dialog class and //pass the hWnd of it into the last parameter in this call pSubMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON, pptPosition->x, pptPosition->y, theApp.m_pMainWnd); // We've shown our own context menu. //MSHTML.DLL will no longer try //to show its own. return S_OK; }
现在是酷的部分。让我们进入Idispimp.cpp文件。在这里,我们可以编写代码,允许我们从HTML页面中的JavaScript调用我们的C++函数,这些HTML页面被加载到我们的自定义浏览器窗口中。
首先,我们必须将以下代码添加到我们Idispimp.cpp文件的顶部,紧挨着includes之后 - 紧挨着#include "idispimp.h"
。我们需要它以便访问我们主对话框中的函数。
#include "CustomBrowser.h" #include "CustomBrowserDlg.h"
转到文件顶部,直到看到下面显示的这段代码。
// Hardcoded information for extending the Object Model // Typically this would be supplied through a TypeInfo // In this case the name "xxyyzz" maps to DISPID_Extend const WCHAR pszExtend[10]=L"xxyyzz"; #define DISPID_Extend 12345
用下面显示的这段代码替换上面显示的这段代码。
//These are used to allow our application to determine //what function it should call in our app based on the //information passed to the IDispatch interface from //javascript CString cszCB_IsOurCustomBrowser = "CB_IsOurCustomBrowser"; CString cszCB_Close = "CB_Close"; CString cszCB_CustomFunction = "CB_CustomFunction"; CString cszCB_CustomFunctionWithParams = "CB_CustomFunctionWithParams"; CString cszCB_OpenWindow = "CB_OpenWindow"; CString cszCB_ShowModalDialog = "CB_ShowModalDialog"; CString cszCB_ShowModelessDialog = "CB_ShowModelessDialog"; #define DISPID_CB_IsOurCustomBrowser 1 #define DISPID_CB_Close 2 #define DISPID_CB_CustomFunction 3 #define DISPID_CB_CustomFunctionWithParams 4 #define DISPID_CB_OpenWindow 5 #define DISPID_CB_ShowModalDialog 6 #define DISPID_CB_ShowModelessDialog 7
现在我们必须找到GetIDsOfNames
函数并编写一些代码,以便它能够将正确的ID传递给invoke函数,这将使我们能够确定要进行哪个C++调用。所以找到下面显示的这段代码。
STDMETHODIMP CImpIDispatch::GetIDsOfNames( /* [in] */ REFIID riid, /* [size_is][in] */ OLECHAR** rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID* rgDispId) { HRESULT hr; UINT i; // Assume some degree of success hr = NOERROR; // Hardcoded mapping for this sample // A more usual procedure would be to use a TypeInfo for ( i=0; i < cNames; i++) { if ( 2 == CompareString( lcid, NORM_IGNOREWIDTH, (char*)pszExtend, 3, (char*)rgszNames[i], 3 ) ) { rgDispId[i] = DISPID_Extend; } else { // One or more are unknown so set the return code accordingly hr = ResultFromScode(DISP_E_UNKNOWNNAME); rgDispId[i] = DISPID_UNKNOWN; } } return hr; }
用下面显示的这段代码替换上面显示的这段代码。
STDMETHODIMP CImpIDispatch::GetIDsOfNames( /* [in] */ REFIID riid, /* [size_is][in] */ OLECHAR** rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID* rgDispId) { HRESULT hr; UINT i; // Assume some degree of success hr = NOERROR; for ( i=0; i < cNames; i++) { CString cszName = rgszNames[i]; if(cszName == cszCB_IsOurCustomBrowser) { rgDispId[i] = DISPID_CB_IsOurCustomBrowser; } else if(cszName == cszCB_Close) { rgDispId[i] = DISPID_CB_Close; } else if(cszName == cszCB_CustomFunction) { rgDispId[i] = DISPID_CB_CustomFunction; } else if(cszName == cszCB_CustomFunctionWithParams) { rgDispId[i] = DISPID_CB_CustomFunctionWithParams; } else if(cszName == cszCB_OpenWindow) { rgDispId[i] = DISPID_CB_OpenWindow; } else if(cszName == cszCB_ShowModalDialog) { rgDispId[i] = DISPID_CB_ShowModalDialog; } else if(cszName == cszCB_ShowModelessDialog) { rgDispId[i] = DISPID_CB_ShowModelessDialog; } else { // One or more are unknown so set the return code accordingly hr = ResultFromScode(DISP_E_UNKNOWNNAME); rgDispId[i] = DISPID_UNKNOWN; } } return hr; }
最后,找到下面显示的Invoke
函数。
STDMETHODIMP CImpIDispatch::Invoke( /* [in] */ DISPID dispIdMember, /* [in] */ REFIID /*riid*/, /* [in] */ LCID /*lcid*/, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS* pDispParams, /* [out] */ VARIANT* pVarResult, /* [out] */ EXCEPINFO* /*pExcepInfo*/, /* [out] */ UINT* puArgErr) { // For this sample we only support a Property Get on DISPID_Extend // returning a BSTR with "Wibble" as the value if ( dispIdMember == DISPID_Extend ) { if ( wFlags & DISPATCH_PROPERTYGET ) { if ( pVarResult != NULL ) { WCHAR buff[10]=L"Wibble"; BSTR bstrRet = SysAllocString( buff ); VariantInit(pVarResult); V_VT(pVarResult)=VT_BSTR; V_BSTR(pVarResult) = bstrRet; } } } return S_OK; }
用下面显示的这段代码替换上面显示的这段代码。
STDMETHODIMP CImpIDispatch::Invoke( /* [in] */ DISPID dispIdMember, /* [in] */ REFIID /*riid*/, /* [in] */ LCID /*lcid*/, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS* pDispParams, /* [out] */ VARIANT* pVarResult, /* [out] */ EXCEPINFO* /*pExcepInfo*/, /* [out] */ UINT* puArgErr) { CCustomBrowserDlg* pDlg = (CCustomBrowserDlg*) AfxGetMainWnd(); if(dispIdMember == DISPID_CB_IsOurCustomBrowser) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { bool bResult = pDlg->CB_IsOurCustomBrowser(); VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = bResult; } } if(dispIdMember == DISPID_CB_Close) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { pDlg->CB_Close(); } } if(dispIdMember == DISPID_CB_CustomFunction) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { pDlg->CB_CustomFunction(); } } if(dispIdMember == DISPID_CB_CustomFunctionWithParams) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { //arguments come in reverse order //for some reason CString cszArg1= pDispParams->rgvarg[1].bstrVal; int nArg2= pDispParams->rgvarg[0].intVal; pDlg->CB_CustomFunctionWithParams(cszArg1, nArg2); } } if(dispIdMember == DISPID_CB_OpenWindow) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { //arguments come in reverse order //for some reason CString cszArg1= pDispParams->rgvarg[5].bstrVal; int nArg2= pDispParams->rgvarg[4].intVal; int nArg3= pDispParams->rgvarg[3].intVal; int nArg4= pDispParams->rgvarg[2].intVal; int nArg5= pDispParams->rgvarg[1].intVal; int nArg6 = pDispParams->rgvarg[0].intVal; pDlg->CB_OpenWindow(cszArg1, nArg2, nArg3, nArg4, nArg5, nArg6); } } if(dispIdMember == DISPID_CB_ShowModelessDialog) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { //arguments come in reverse order //for some reason CString cszArg1= pDispParams->rgvarg[4].bstrVal; int nArg2= pDispParams->rgvarg[3].intVal; int nArg3= pDispParams->rgvarg[2].intVal; int nArg4= pDispParams->rgvarg[1].intVal; int nArg5= pDispParams->rgvarg[0].intVal; pDlg->CB_ShowModelessDialog(cszArg1, nArg2, nArg3, nArg4, nArg5); } } if(dispIdMember == DISPID_CB_ShowModalDialog) { if(wFlags & DISPATCH_PROPERTYGET) { if(pVarResult != NULL) { VariantInit(pVarResult); V_VT(pVarResult)=VT_BOOL; V_BOOL(pVarResult) = true; } } if ( wFlags & DISPATCH_METHOD ) { //arguments come in reverse order //for some reason CString cszArg1= pDispParams->rgvarg[4].bstrVal; int nArg2= pDispParams->rgvarg[3].intVal; int nArg3= pDispParams->rgvarg[2].intVal; int nArg4= pDispParams->rgvarg[1].intVal; int nArg5= pDispParams->rgvarg[0].intVal; pDlg->CB_ShowModalDialog(cszArg1, nArg2, nArg3, nArg4, nArg5); } } return S_OK; }
现在我们只需要将所有这些函数添加到我们的CCustomBrowserDlg
类中。所以,进入CustomBrowserDlg.h文件,并添加下面显示的以下公共函数声明。
void CB_ShowModelessDialog(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight); void CB_ShowModalDialog(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight); void CB_OpenWindow(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight, int nResizable); void CB_CustomFunctionWithParams(CString cszString, int nNumber); void CB_CustomFunction(); void CB_Close(); BOOL CB_IsOurCustomBrowser();
现在,进入CustomBrowserDlg.cpp文件,并添加下面显示的以下函数。
注意:下面显示的函数只是为了简单起见,显示一个带有所有传入参数的消息框。在演示项目中,实际上有代码来打开窗口、模态对话框和非模态对话框。我只是不想解释如何创建窗口、模态和非模态对话框,因为它超出了本文的范围。当然,您可以在演示项目中看到所有这些代码是如何实际工作的。
void CCustomBrowserDlg::CB_Close() { AfxMessageBox("Close the browser here or the current window"); //This is one way you can determine whether or not //to close a dialog or the main application depending //on if you call the CB_Close method from an html page //in a dialog/window or from an html page in the main app //for example if you launch a modal dialog from your javascript code //and you want to have your own close button in your html page as //an alternative to using the x button, then you can just call //window.external.CB_Close(); rather than window.close(); and this //function will determine which window to close based on which //window is currently active using the code below. /* CWnd* pWnd = GetActiveWindow(); if(pWnd == this) { EndDialog(0); } else { CDialog* pWin = (CDialog*)pWnd; pWin->EndDialog(1); } */ } void CCustomBrowserDlg::CB_CustomFunction() { AfxMessageBox("Do whatever you like here!"); } void CCustomBrowserDlg::CB_CustomFunctionWithParams (CString cszString, int nNumber) { CString cszParameters; cszParameters.Format ("parameter 1: %s\nparameter 2: %d", cszString, nNumber); AfxMessageBox(cszParameters); } void CCustomBrowserDlg::CB_OpenWindow(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight, int nResizable) { //you could launch a normal window from here //I won't show how, but it is pretty easy. //I just wanted to show you how to pass parameters CString cszParameters; cszParameters.Format("URL=%s LEFT=%d TOP=%d WIDTH=%d HEIGHT=%d RESIZABLE=%d", cszURL, nLeft, nTop, nWidth, nHeight, nResizable); AfxMessageBox(cszParameters); } void CCustomBrowserDlg::CB_ShowModalDialog(CString cszURL, int nLeft, int nTop, int nWidth, int nHeight) { //you could launch a modal dialog from here //I won't show how, but it is pretty easy. //I just wanted to show you how to pass parameters CString cszParameters; cszParameters.Format("URL=%s LEFT=%d TOP=%d WIDTH=%d HEIGHT=%d RESIZABLE=%d", cszURL, nLeft, nTop, nWidth, nHeight); AfxMessageBox(cszParameters); } void CCustomBrowserDlg::CB_ShowModelessDialog (CString cszURL, int nLeft, int nTop, int nWidth, int nHeight) { //you could launch a modeless dialog from here //I won't show how, but it is pretty easy. //I just wanted to show you how to pass parameters CString cszParameters; cszParameters.Format("URL=%s LEFT=%d TOP=%d WIDTH=%d HEIGHT=%d RESIZABLE=%d", cszURL, nLeft, nTop, nWidth, nHeight); AfxMessageBox(cszParameters); }
我们添加到CCustomBrowserDlg
类中的所有这些函数现在都可以从JavaScript访问了!如下所示。下面显示的这段代码来自包含在示例项目中的CustomTest.html文件。
<script langauge="javascript">
function fnIsOurCustomBrowser()
{
//Check to see if this is our custom
//browser. We just see if the function
//IsOurCustomBrowser is available. By leaving
//the brackets off of our function it is treated
//as a property and thus allows us to check if
//our method exists in the external window. If it
//returns null then obviously this is not our custom
//browser, so we return false.
if(window.external.CB_IsOurCustomBrowser!=null)
return true;
else
return false;
}
//setup a variable to let us know if this is our
//custom browser.
bIsCustomBrowser = fnIsOurCustomBrowser();
if(!bIsCustomBrowser)
{
//You can check here to see if they have our custom browser.
//If they don't you can do whatever you want, redirect them
//to another page or whatever. For the purposes of this example
//I will just alert them
alert('You must be using Custom Browser to
view this page properly.');
}
function fnCallFunction()
{
//Call c++ function that we made
//in our custom browser
if(bIsCustomBrowser)
window.external.CB_CustomFunction();
}
function fnCallFunctionWithParams(strString, nNumber)
{
//Call c++ function that accepts parameters
//that we made in our custom browser
if(bIsCustomBrowser)
window.external.CB_CustomFunctionWithParams
(strString, nNumber);
}
function fnOpenWindow(strURL, nLeft, nTop,
nWidth, nHeight, nResizable)
{
if(bIsCustomBrowser)
window.external.CB_OpenWindow(strURL, nLeft,
nTop, nWidth, nHeight, nResizable);
}
function fnShowModalDialog(strURL, nLeft,
nTop, nWidth, nHeight)
{
if(bIsCustomBrowser)
window.external.CB_ShowModalDialog(strURL, nLeft,
nTop, nWidth, nHeight);
}
function fnShowModelessDialog(strURL,
nLeft, nTop, nWidth, nHeight)
{
if(bIsCustomBrowser)
window.external.CB_ShowModelessDialog(strURL, nLeft,
nTop, nWidth, nHeight);
}
</script>
关注点
- 创建一个展现本文所有功能的自定义网页浏览器,不像人们最初想的那么直接。
- 一旦你在你的对话框式应用程序中为你的网页浏览器控件实现了接口,你就有了一个相当不错的框架,可以在你需要提供任何自定义浏览器解决方案时使用。
- 我在找到创建从JavaScript调用C++函数的能力所需的必要条件之前,研究了数小时。
已知问题
- 如果你在编译源代码时遇到问题,那么你必须从Microsoft安装Platform SDK的Internet Development SDK部分。即使你认为你已经安装了最新的平台SDK,请再次安装Internet Development SDK。这应该能解决问题。
致谢
- Microsoft知识库文章 - 236312