分类: C/C++
2008-08-27 13:38:04
I am a web developer by day, but a C++/MFC coder by night! While I was doing my day job, our client requested that we provide them a solution to ensure that they had a way to allow their employees to take exams and view course content over the Internet in a more secure fashion than regular IE, so that their employees couldn't cheat or copy content and sell it on eBay very easily. So I figured if I could create my own window that goes full screen, I could hide the task bar, disable keystrokes, and whatever I needed to do to lock down the system while my app was open. However I needed to be able to customize the Internet Explorer Web Browser control pretty heavily (custom context menus, dialogs, windows, c++ calls from JavaScript, message boxes, and override keystrokes within the browser window).
I thought I would try coding it in C# and try some of this .NET stuff out on something more complex than a simple database application, but I quickly realized that video wouldn't even play in the HTML page I was loading through the C# implementation (Big problem for us considering we create interactive media solutions). Not to mention, having to make a function declaration for every raw Win32 API call I wanted to make, was really trying my patience. I even tried the .NET CF library, but it was missing many functions I needed. I ported some C++ code I had written to intercept keystrokes using a system wide hook, over to C# and when I did finally get it all ported over to C#, it crashed randomly and only trapped some of the keys I needed (C# may do garbage collection for you, but that doesn't mean your app won't crash). Needless to say, I just decided to start coding it in C++/MFC (long live C++ 6.0 MFC!).
I began my journey of researching how to make a custom web browser using the Microsoft WebBrowser
control that would allow me to have custom message boxes, custom modal dialogs, custom windows, custom context menus, call C++ code from JavaScript, and disable specific keystroke combinations. I found a ton of documents on MSDN and other sites telling me that I have to implement the IDocHostShowUI
and IDocHostUIHandler
COM interfaces and IDispatch
something or rather. The only problem was I didn't know what that meant or how to do it. The source code I did find, only did a small part of what I needed to do and used a CHtmlView
while I needed it to work in a dialog based application. The source code also left me in the dark on many of the other tasks I needed to do.
Using a single article that I found in the Microsoft knowledge base (after hours and hours of searching) and then writing and tweaking a little code, I was able to finally get all of the functionality I required in my custom web browser application.
Before we start, I know this looks scary because of all the header files and CPP files we are going to have to add as well as the sheer length of this article, but it really isn't nearly as complicated or scary as it may seem. That's important to realize I think, because I know I almost got scared away when I started to read that knowledge base article too :)
I must say a lot of credit for this goes to Microsoft Knowledge Base Article - 236312. It was a big factor in allowing me to create a custom web browser solution that was flexible enough to meet my requirements.
First thing you have to do is create a dialog based application using the MFC AppWizard. Next go to the ResourceView tab in Visual Studio and open your dialog in design mode. Now go to "Projects", "Add to Project", "Components and Controls...". Scroll through the controls in the Registered ActiveX Controls folder until you find Microsoft Web Browser. Now click on that and click the insert button and then close the Components and Controls Gallery dialog window.
Now if you look at the controls toolbar in the design view of your dialog based app, you will see a new item that has a globe for an icon. That is our web browser control. So just click on that item and put it on your dialog box. Now press (Ctrl+W) to bring up the class wizard and add a member variable called m_browser
for IDC_EXPLORER1
.
Now here is where the knowledge base article I mentioned earlier comes into play. I would normally just refer you to the knowledge base article, but we all know how reliable Microsoft's links are. Therefore I will basically copy the instructions from the knowledge base article and paste below.
CCustomBrowserApp
class: public: class CImpIDispatch* m_pDispOM;
//=---------------------------------------------------------------= // (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
//=------------------------------------------------------------------= // (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 * 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 * 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; }
InitInstance
of CCustomBrowser
, add the following code. Also comment out the call to 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 }
#include "afxpriv.h" #include <..\src\occimpl.h> #include "CustSite.h"
extern CCustomBrowserApp theApp;
Now that we have all of that done, we have a good start, but we still have ways to go. Basically what we have done so far is implement the IDocHostUIHandler
and the IDispatch
interfaces. This will allow us to do things like customize the context menus and call C++ functions from our JavaScript code using window.external
. However, we also want to implement the IDocHostShowUI
interface, so we can provide our own custom message boxes. We also need to write some extra code to make it possible to open modal and modeless dialog boxes using our custom web browser, create C++ functions that our JavaScript can call, and show a custom context menu.
We will start by implementing the IDocHostShowUI
interface.
public
just below 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)
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; }
Now we have finished implementing all the interfaces that we need in order to customize the browser to the extent we need to. Now all we have to do is write the code to actually do our customizations and we have a framework to use whenever we need to provide a custom web browser solution that requires advanced functionality.
First lets go into the Custsite.cpp file. In this file is where we can customize our message boxes. In other words every time someone calls the function alert('some text here');
from JavaScript they will see our custom message box. Find the function called ShowMessage
and inside that function we will make it display a critical error image instead of the standard warning exclamation icon just for an example:
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; }
If you want to, you can also trap common key combinations and cancel them or make them do different things. To disable some common key combinations go into the Custsite.cpp file and find the piece of code shown below:
HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::TranslateAccelerator (LPMSG lpMsg, /* [in] */ const GUID __RPC_FAR *pguidCmdGroup, /* [in] */ DWORD nCmdID) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) return S_FALSE; }
Replace the code shown above with the following code below:
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; }
You can also supply your own custom context menu instead of Internet Explorer's by putting code in the ShowContextMenu
handler as shown below:
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; }
Now the cool part. Lets go into the file Idispimp.cpp. This is where we can write code to allows us to call our C++ functions from JavaScript on the HTML pages, that get loaded into our custom browser window.
First we must add the following code to the top of our Idispimp.cpp file just below the includes - just below #include "idispimp.h"
. We need this so we can access functions in our main dialog:
#include "CustomBrowser.h" #include "CustomBrowserDlg.h"
Go to the top of the file until you see the code shown below:
// 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
Replace the block of code shown above with the code below:
//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
Now we have to find the function GetIDsOfNames
and write some code so it can pass the proper ID to the invoke function which will allow us to determine which C++ call to make. So find the code shown below:
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; }
Replace the code above with this code shown below:
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; }
Now lastly find the Invoke
function shown below:
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; }
Replace the code above with this code shown below:
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; }
Now we just need to add all these functions to our CCustomBrowserDlg
class. So go into the CustomBrowserDlg.h file and add the following public function declarations as shown below:
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();
Now go into the CustomBrowserDlg.cpp file and add the following functions as shown below:
Note: The functions shown below just show a message box with all the parameters passed in just for simplicity sake. In the demo project, there is actually code to open a window, modal dialog, and modeless dialog. I just didn't want to explain how to create windows, modal, and modeless dialog boxes, as it is out of scope for this article. Of course you can see how all this code actually works in the demo project.
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); }
All of these functions that we have added to our CCustomBrowserDlg
class are now accessible from JavaScript! as shown below. The code shown below is from the CustomTest.html file that is included in the sample project.