IDocHostUIHandler Extended CHtmlView

I recently wanted to exploit the features of Internet Explorer to prevent users from selecting text in my CHtmlView. The current implementation does not support a simple way to do this (such as an EnableTextSelection function call), so I decided to investigate.

Here are some of the 'hidden' features I want to expose...

There is a Microsoft example called 'Driller' which is the basis of a solution, but the problem is that it was written before the introduction of CHtmlView, so I began to enhance it.

What we need to do

The functionality we want is part of the IDocHostUIHandler interface. Therefore, we need to find a way of replacing it with our own version. We do this by replacing the ActiveX control container.

Step 1: Creating the container

We need to create two classes: one derived from COleControlSite, the other from COccManager.

Here is a rough outline of the format for ROleControlSite.h...


#include <mshtmhst.h>

class ROleControlSite : public COleControlSite
{
public:
	ROleControlSite(COleControlContainer *pCnt = NULL):COleControlSite(pCnt) {}

protected:

DECLARE_INTERFACE_MAP();
BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler)

// interface functions here

END_INTERFACE_PART(DocHostUIHandler)
BEGIN_INTERFACE_PART(DocHostShowUI, IDocHostShowUI)

// interface functions here

END_INTERFACE_PART(DocHostShowUI)
};


class ROccManager : public COccManager
{
public:
	ROleControlSite *m_pROleControlSite;

	ROccManager() { m_pROleControlSite = NULL; }

	COleControlSite* CreateSite(COleControlContainer* pCtrlCont)
	{
		m_pROleControlSite = new ROleControlSite(pCtrlCont);
		return m_pROleControlSite;
	}
};

The functions for the interface are defined in ROleControlSite.cpp. This is where we tell Internet Explorer the properties we want for the browser. It is very important that the #include <../src/occimpl.h> is as written.

Here is a rough outline of the format for ROleControlSite.cpp...


#include "stdafx.h"
#undef AFX_DATA
#define AFX_DATA AFX_DATA_IMPORT
#include <..\src\occimpl.h>
#undef AFX_DATA
#define AFX_DATA AFX_DATA_EXPORT

#include "ROleControlSite.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


BEGIN_INTERFACE_MAP(ROleControlSite, COleControlSite)
	INTERFACE_PART(ROleControlSite, IID_IDocHostUIHandler, DocHostUIHandler)
	INTERFACE_PART(ROleControlSite, IID_IDocHostShowUI, DocHostShowUI)
END_INTERFACE_MAP()

// implementation of the interface's functions here
// ...

Step 2: Using our container

When MFC creates a CHtmlView, it calls AfxEnableControlContainer(). This is the key to our solution. If we pass our own container as a parameter, then it will use our interfaces that we defined.

We add a new class derived from CHtmlView called RHtmlView. We then include the files necessary to use our container in RHtmlView.cpp. All .cpp files which use RHtmlView must include the following files, however the strict #include in ROleControlSite.cpp is not necessary here...


#include <..\src\occimpl.h>
#include "ROleControlSite.h"

Next, we add a member variable to RHtmlView for the container...


ROccManager m_pROccManager;

In our RHtmlView class, we replace the Create function with this...


BOOL RHtmlView::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle,
	const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext) 
{
	///////////////////////////////////
	// The following does the same as MFC source, except that
	// AfxEnableControlContainer() is called with our handler.
	///////////////////////////////////

	CRect c_clientRect;
	GetClientRect(&c_clientRect);

	///////////////////////////////////
	// create the view window:

	m_pCreateContext = pContext;

	if (!CView::Create(lpszClassName, lpszWindowName, dwStyle, rect,
		pParentWnd,  nID, pContext))
	{
		return FALSE;
	}

	AfxEnableControlContainer(&m_pROccManager);

	///////////////////////////////////
	// create the control window:

	if (!m_wndBrowser.CreateControl(CLSID_WebBrowser, lpszWindowName,
				WS_VISIBLE | WS_CHILD, c_clientRect, this, AFX_IDW_PANE_FIRST))
	{
		DestroyWindow();
		return FALSE;
	}

	LPUNKNOWN lpUnk = m_wndBrowser.GetControlUnknown();
	HRESULT hr = lpUnk->QueryInterface(IID_IWebBrowser2, (void**) &m_pBrowserApp);
	if (!SUCCEEDED(hr))
	{
		m_pBrowserApp = NULL;
		m_wndBrowser.DestroyWindow();
		DestroyWindow();
		return FALSE;
	}

	///////////////////////////////////
	// Our initialisation:

	SetWindowText("RHtmlView");
	SetClassLong(this->m_hWnd, GCL_STYLE, CS_DBLCLKS);

	///////////////////////////////////

	return TRUE;	
}

At this point, we have a working RHtmlView which uses our IDocHostUIHandler code. Note the use of SetClassLong in RHtmlView::Create to remove irritating flicker when resizing.

Step 3: Specifying our own properties

When a page is loaded, the GetHostInfo function is called. This is where we specify flags for the user interface of the control. Flags are defined in the MSDN Visual Studio Reference.

I have added extra functions to ROleControlSite to allow storage of the flags. The following is our definition of GetHostInfo...


// GetHostInfo
STDMETHODIMP ROleControlSite::XDocHostUIHandler::GetHostInfo(
		/* [i/o] */	DOCHOSTUIINFO __RPC_FAR *pInfo)
{
	METHOD_PROLOGUE(ROleControlSite, DocHostUIHandler)

	pInfo->cbSize = sizeof(DOCHOSTUIINFO);
	pInfo->dwFlags = pThis->GetXDocHostUI_Flag();
	pInfo->dwDoubleClick = pThis->GetXDocHostUI_DblClk();

	return S_OK;
}

For the context-menu flag, we have to add some extra code like so...


// ShowContextMenu
STDMETHODIMP ROleControlSite::XDocHostUIHandler::ShowContextMenu(
		/* [in ] */	DWORD dwID,
		/* [in ] */	POINT __RPC_FAR *ppt,
		/* [in ] */	IUnknown __RPC_FAR *pcmdtReserved,
		/* [in ] */	IDispatch __RPC_FAR *pdispReserved)
{
	METHOD_PROLOGUE(ROleControlSite, DocHostUIHandler)

	// Don't show context menu
	if (pThis->GetXDocHostUI_Flag() & DOCHOSTUIFLAG_DISABLE_HELP_MENU) return S_OK;

	// Otherwise, show default
 	return S_FALSE;
}

Step 4: Usability

With the implementation so far, we must take complicated steps to change the flags from outside of ROleControlSite.cpp. Therefore, much of this is dealt with by adding functions to RHtmlView. Here is the complete list...


// XDocHostUI_DblClk interface
	DWORD GetXDocHostUI_DblClk();
	void SetXDocHostUI_DblClk(DWORD dwSet);

// XDocHostUI_Flag interface
	DWORD GetXDocHostUI_Flag();
	void SetXDocHostUI_Flag(DWORD dwSet);

	BOOL GetXDocHostUIFlag_Dialog();
	BOOL GetXDocHostUIFlag_DisableHelpMenu();
	BOOL GetXDocHostUIFlag_No3dBorder();
	BOOL GetXDocHostUIFlag_ScrollNo();
	BOOL GetXDocHostUIFlag_DisableScriptInactive();
	BOOL GetXDocHostUIFlag_OpenNewWin();
	BOOL GetXDocHostUIFlag_DisableOffscreen();
	BOOL GetXDocHostUIFlag_FlatScrollbar();
	BOOL GetXDocHostUIFlag_DivBlockDefault();
	BOOL GetXDocHostUIFlag_ActivateClienthitOnly();

	void SetXDocHostUIFlag_Dialog(BOOL bSet);
	void SetXDocHostUIFlag_DisableHelpMenu(BOOL bSet);
	void SetXDocHostUIFlag_No3dBorder(BOOL bSet);
	void SetXDocHostUIFlag_ScrollNo(BOOL bSet);
	void SetXDocHostUIFlag_DisableScriptInactive(BOOL bSet);
	void SetXDocHostUIFlag_OpenNewWin(BOOL bSet);
	void SetXDocHostUIFlag_DisableOffscreen(BOOL bSet);
	void SetXDocHostUIFlag_FlatScrollbar(BOOL bSet);
	void SetXDocHostUIFlag_DivBlockDefault(BOOL bSet);
	void SetXDocHostUIFlag_ActivateClienthitOnly(BOOL bSet);

An important issue to note is that we must call Navigate2 with the current location in order to dynamically change the properties, otherwise we see no effect. Therefore, I also updated some standard CHtmlView functions...


void RHtmlView::SetRegisterAsBrowser(BOOL bNewValue)
{
	CHtmlView::SetRegisterAsBrowser(bNewValue);
	Navigate2(GetLocationURL(), 0, NULL, NULL);
}

void RHtmlView::SetRegisterAsDropTarget(BOOL bNewValue)
{
	CHtmlView::SetRegisterAsDropTarget(bNewValue);
	Navigate2(GetLocationURL(), 0, NULL, NULL);
}

void RHtmlView::SetSilent(BOOL bNewValue)
{
	CHtmlView::SetSilent(bNewValue);
	Navigate2(GetLocationURL(), 0, NULL, NULL);
}

void RHtmlView::SetTheaterMode(BOOL bNewValue)
{
	CHtmlView::SetTheaterMode(bNewValue);
	Navigate2(GetLocationURL(), 0, NULL, NULL);
}

Step 5: Adding RHtmlView to a project

First of all, create a view derived from CHtmlView. You should edit the source files, replacing all references of CHtmlView with RHtmlView.

Add the files RHtmlView.cpp, RHtmlView.h, ROleControlSite.cpp, and ROleControlSite.h to your project.

In all files which #include your view's header file, #include the following before the view's header file is #included...


#include <..\src\occimpl.h>
#include "ROleControlSite.h"

You should now have the extra functionality. Note that all RHtmlView's created can have different properties.

Possible Enhancements

  • Add functions to control downloading Images, Videos, and ActiveX controls. (the AtlBrowser sample does this)
  • Extend the DOM by implementing an IDispatch interface, and passing it in GetExternal.
  • Add the ability to stop the browser acting as a drag+drop server.

Download

Download demo project (and Release Executable) - 75 Kb
Download source - 6 Kb


Comments

  • Fix for error when compile under vc++ 8.0

    Posted by na10na10 on 06/24/2007 12:08pm

    // Compiled VC++ 8.0
    
    class ROccManager :public COccManager
    {
    public:
    	ROleControlSite *m_pROleControlSite;
    	COleControlContainer *m_pContainer;
    	ROccManager()
    	{
    		m_pContainer = NULL;
    		m_pROleControlSite = NULL;
    	}
    	~ROccManager()
    	{
    		delete m_pROleControlSite;
    		delete m_pContainer;
    	}
    	COleControlSite* CreateSite(COleControlContainer* pCtrlCont)
    	{
    		m_pContainer = pCtrlCont;
    		m_pROleControlSite = new ROleControlSite(pCtrlCont);
    		return m_pROleControlSite;
    	}
    };
    
    
    BOOL RHtmlView::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext) 
    {
    	///////////////////////////////////
    	// The following does the same as MFC source, except that
    	// AfxEnableControlContainer() is called with our handler.
    	///////////////////////////////////
    
    	
    	///////////////////////////////////
    	// create the view window:
    
    	m_pCreateContext = pContext;
    
    	if (!CView::Create(lpszClassName, lpszWindowName, dwStyle, rect,
    		pParentWnd,  nID, pContext))
    	{
    		return FALSE;
    	}
    	
    	///////////////////////////////////
    	// create the control window:
    	CRect c_clientRect;
    	GetClientRect(&c_clientRect);
    
    
    	if (!m_wndBrowser.CreateControl(CLSID_WebBrowser, lpszWindowName,
    				WS_VISIBLE | WS_CHILD, rect, this, AFX_IDW_PANE_FIRST))
    	{
    		DestroyWindow();
    		return FALSE;
    	}
    	//LPDISPATCH pIContainer = GetContainer();
    
    	m_pROccManager = new ROccManager;
    	COleControlContainer *pContainer = m_pROccManager->CreateContainer(this);
    	m_pROccManager->CreateSite(pContainer);
    	AfxEnableControlContainer(m_pROccManager);
    
    	LPUNKNOWN lpUnk = m_wndBrowser.GetControlUnknown();
    	HRESULT hr = lpUnk->QueryInterface(IID_IWebBrowser2, (void**) &m_pBrowserApp);
    	if (!SUCCEEDED(hr))
    	{
    		m_pBrowserApp = NULL;
    		m_wndBrowser.DestroyWindow();
    		DestroyWindow();
    		return FALSE;
    	}
    
    	///////////////////////////////////
    	// Our initialisation:
    
    	SetWindowText("RHtmlView");
    	SetClassLong(this->m_hWnd, GCL_STYLE, CS_DBLCLKS);
    
    	///////////////////////////////////
    
    	return TRUE;	
    }

    Reply
  • OccManager Should not be member of view class

    Posted by Legacy on 10/28/2003 12:00am

    Originally posted by: D. Plate

    After doing some debugging in a similar class, I think I
    
    see a problem if you try to use this in an MDI
    application. I've seen a few samples like this that are
    based on the classic "Driller" sample and have a similar
    problem.

    As shown in the example source code of the article, there
    is a member variable,

    ROccManager m_pROccManager;

    of the ROccManager class. A pointer to that member is
    passed through AfxEnableControlContainer() to wire the
    OccManager into the framework.

    The problem is that the thread global pointer afxOccManager
    is set to the pointer that is passed in through
    AfxEnableControlContainer(). At least it is in VC6. In an
    MDI application, I'm pretty sure this will cause problems
    because the CWnd class calls methods of the afxOccManager
    object in some cases. For example, CWnd::IsDialogMessage()
    can call afxOccManger->IsDialogMessage(). This means that
    if the View that conatins the OccManager gets closed, other
    Views may still try and call methods of that deleted object
    by way of afxOccManager, and crash.

    What I did for my project was to create a single OccManager
    object and install it once in InitiInstance() of the app. I
    suggest declaring something like this in your application
    class:

    class ROccManager *m_pROccManager;

    Then in InitInstance() of the application class, create the
    object and wire it in:

    BOOL InitInstance()
    {
    m_pROccManager = new ROccManager;

    // If AppWizard has already put a call to
    // AfxEnableControlContainer() in InitInstance,
    // replace it with this one that has the pointer
    // argument. Calling this without an argument
    // elsewhere in the app will unhook your OccManager.

    AfxEnableControlContainer(pROccManager);.

    //... other initialization stuff...
    }

    Don't call AfxEnableControlContainer() again in the
    View class or anywhere else in the app - you won't need to. You can also do away with
    the OccManager member in the ROccManager class and at a
    glance, I don't see why the ControlSite pointer needs to be
    saved in a member variable. In VC6, CWnd class keeps track
    of the ControlSite and deletes in when the window is
    destroyed (I think).

    If as someone else suggested, this causes troubles for
    other controls, you might make the OccManager more
    elaborate to pass Views that aren't of the your class on to
    the base class implementation (guessing - haven't tried
    that).

    Reply
  • Some fix

    Posted by Legacy on 01/10/2003 12:00am

    Originally posted by: DarkMist

    If you have another controls in your application you have to restore OccManager after control's creating.
    
    If you not do so, it causes traps in mshtml(IE6.0).

    BOOL RHtmlView::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)
    {
    // ...

    AfxEnableControlContainer(&m_pROccManager);

    if (!m_wndBrowser.CreateControl(CLSID_WebBrowser, lpszWindowName, WS_VISIBLE | WS_CHILD, c_clientRect, this, AFX_IDW_PANE_FIRST))
    {
    DestroyWindow();
    return FALSE;
    }

    // add this line
    AfxEnableControlContainer();

    // ...
    }

    Reply
  • Fix for the assertion error when closing

    Posted by Legacy on 08/25/2002 12:00am

    Originally posted by: Jos� Leandro Massada

    Well i got this error because i used the class inside a CFormView. Why do I got the error? Because the view wasn't allocated directly from the heap. The solution is to override PostNcDestroy(). PostNcDestroy() does a "delete this;" to destroy the view, and that's the normal procedure to destroy a view allocated directly from the heap.
    So, just override PostNcDestroy() and remove CHtmlView::PostNcDestroy();

    void CMyDerivedHtmlViewFromRHtmlView::PostNcDestroy()
    {

    }

    regards.

    Reply
  • Would not compile under VC++ 7.0

    Posted by Legacy on 08/01/2002 12:00am

    Originally posted by: S. Bob

    MFC implementation of the client site in VC++ 6.0 may be different from that of VC++ 7.0.

    Reply
  • Can't get my .arx dll to compile

    Posted by Legacy on 07/25/2002 12:00am

    Originally posted by: Marc Loxton

    MainDlg.cpp
    c:\documents and settings\spoonchops\my documents\programming\myprojects\thesis\maindlg.h(15) : error C2143: syntax error : missing ';' before '<class-head>'
    c:\documents and settings\spoonchops\my documents\programming\myprojects\thesis\maindlg.h(15) : fatal error C1004: unexpected end of file found
    Error executing cl.exe.
    Creating browse info file...
    BSCMAKE: error BK1506 : cannot open file '.\Debug\MainDlg.sbr': No such file or directory
    Error executing bscmake.exe.

    AsdkProject.arx - 3 error(s), 0 warning(s)


    CMainDlg class is a standard CDialog based class, it throws an error on the class definition in it's h file and I have no idea why. I don't think it has anything to with with the definition as I haven't changed it maybe include file problem?? I am at a total loss so any pointers would be appreciated, thanks in advance

    Reply
  • Unhandled exception error

    Posted by Legacy on 06/26/2002 12:00am

    Originally posted by: David

    Instead of loading an url from a resource. Navigate2(http://www.yahoo.com/";) then open up 2 or more windows. Now start to close them and the app will crash.

    Reply
  • No Scroll Bar ???

    Posted by Legacy on 03/29/2002 12:00am

    Originally posted by: Ganesan

    Some how the scroll bars dont disappear when I click "no scroll bar" option. Can someone please tell me how to get rid of the scrollbar in CHtmlView?

    Thanks ...

    Reply
  • Assertion error in debug mode

    Posted by Legacy on 03/05/2002 12:00am

    Originally posted by: Rakesh Sharma

    When I tried to run you code in debug mode it give Assertion Failed in file Afxwin2.inl at line 98.
    Can you please help me to overcome this problem.

    Reply
  • Context Menus

    Posted by Legacy on 10/19/2001 12:00am

    Originally posted by: Ben

    Great Job on the class!

    I needed a way to not display the standard context menu for IE and this did the job. I am having problems though trying to display my own context menu when the user right clicks. OnRButtonUp and OnContextMenu are not getting called. Any Ideas would be really appreciated.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Due to internal controls and regulations, the amount of long term archival data is increasing every year. Since magnetic tape does not need to be periodically operated or connected to a power source, there will be no data loss because of performance degradation due to the drive actuator. Read this white paper to learn about a series of tests that determined magnetic tape is a reliable long-term storage solution for up to 30 years.

Most Popular Programming Stories

More for Developers

RSS Feeds