Click here to Skip to main content
13,145,851 members (62,837 online)
Click here to Skip to main content
Add your own
alternative version


74 bookmarked
Posted 27 Mar 2004

Using IHTMLEditDesigner

, 24 Apr 2004
Rate this:
Please Sign up or sign in to vote.
Using IHTMLEditDesigner to modify IE's editing behaviour
<!-- Download Links --> <!-- Add the rest of your HTML here -->


My current project requires the ability to do WYSIWYG HTML editing. A quick look at the MFC class heirarchy revealed CHtmlEditView. An even quicker session with the MFC AppWizard and I had an SDI WYSIWYG HTML editor up and running, using the MSHTML COM object that ships as part of Internet Explorer.

My application, however, doesn't create arbitrary web pages. To create a new page you select a template HTML file and alter existing content. It was important that the layout of the page remain substantially unaltered. This meant that, for example, it should be possible to replace a placeholder image with a real image, resize it to fit the allotted space but not be able to drag the image to a different location on the page.

Solving this little problem turned out to be quite an interesting exercise.


I used CHtmlEditView, one of the new classes introduced in MFC 7. It's a CHtmlView derived class and one of the very very few MFC classes that uses multiple inheritance. It's derived from both the CHtmlView and the CHtmlEditCtrlBase classes. The view inheritance lets it be used in document/view applications. The CHtmlEditCtrlBase adds a whole bunch of capabilities related specifically to HTML editing.

We're not really going to be discussing either of those base classes. However, the CHtmlView class contains a CWnd member which becomes the MSHTML COM object hosted within the class, and a pointer to an IWebBrowser2 COM interface. That interface, in turn, contains methods to navigate to new pages, refresh the current page and so on. We're not interested, for the purpose of this article, in that interface because it's primarly oriented toward browsing.


This is the COM object hiding behind Microsoft Internet Explorer. Internet Explorer itself is little more than a wrapper around MSHTML. This is actually pretty cool from our perspective because it means that our software can host MSHTML and obtain, almost for free, HTML display and editing capabilities. MSHTML includes a complete WYSIWYG HTML editor. All we have to do is host the MSHTML object and provide a user interface. All?

Well there's the little matter of understanding the Document Object Model (DOM) and making sense of a couple of hundred COM interfaces.

I don't intend to exhaustively discuss either the DOM or the many COM interfaces. This article is focussed on demonstrating how to modify the editors behaviour.

Edit Designers

As Internet Explorer evolves so too does MSHTML evolve, providing us with more and more ways to interrogate and control HTML display. Version 5.5 introduced Edit Designers and the IHTMLEditDesigner interface. This interface is essentially a collection of 4 callbacks which MSHTML makes to code we control. Each callback handles MSHTML editing events at various points in the lifetime of the event. The lifetime points are.
  • PreHandleEvent
  • PostHandleEvent
  • TranslateAccelerator
  • PostEditorEventNotify
Of these callbacks PreHandleEvent() is probably the most useful. MSHTML calls into our code to notify us that it's about to do something and we have the chance to modify that behaviour or to cancel it entirely.

So, inspired by what you've read so far, you eagerly fire up your copy of VS .NET, go to the help index and type in IHTMLEditDesigner. You read the description of the PreHandleEvent() method and see that the prototype for the method is. <pre lang=c++> HRESULT PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj); And the comments say this:

'The DISPID parameter provides the most efficient way for an IHTMLEditDesigner method to determine what type of event triggered the method call. The DISPID_HTMLELEMENTEVENTS2 identifiers are defined in Mshtmdid.h.' (Direct quote from MSDN help in VS .NET 2003).


So let's code it and see how it works. This is the header for my CMSHTMLDisableDragHTMLEditDesigner class derived from IHTMLEditDesigner. (Can I lay a claim to the longest classname on CP?). <pre lang=c++> class CMSHTMLDisableDragHTMLEditDesigner : public IHTMLEditDesigner { public: virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject); virtual ULONG STDMETHODCALLTYPE AddRef(void); virtual ULONG STDMETHODCALLTYPE Release(void); virtual HRESULT STDMETHODCALLTYPE PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj); virtual HRESULT STDMETHODCALLTYPE PostHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj); virtual HRESULT STDMETHODCALLTYPE TranslateAccelerator(DISPID inEvtDispId, IHTMLEventObj *pIEventObj); virtual HRESULT STDMETHODCALLTYPE PostEditorEventNotify(DISPID inEvtDispId, IHTMLEventObj *pIEventObj); CMSHTMLDisableDragHTMLEditDesigner(); BOOL Attach(IHTMLDocument2 *pDoc); void Detach(); private: IHTMLEditServices *m_pServices; UINT m_uRefCount; CMSHTMLDisableDragIDispatch m_dp; }; IHTMLEditDesigner is derived from IUnknown so we must provide those standard methods.

Adding an Event Designer to the editor

Let's be very clear on this. MSHTML would probably (if it were sentient) rather we didn't go messing about with its event handling. So it's not about to go and create an IHTMLEditDesigner instance just because we wrote one. It doesn't even know our Edit Designer exists! We (the application writer hosting MSHTML in our application) want to modify MSHTML's behaviour, so we're the ones who have to create an instance of our IHTMLEditDesigner derived object and tell MSHTML to use it. Because we're the ones creating the object we get to decide if it's going to live in a COM DLL or in our exe file (the one that's hosting MSHTML). If it's in our exe file there's no need for CoCreateInstance(). Just do a m_designer = new CMSHTMLDisableDragHTMLEditDesigner or embed an instance of the object in your view and let c++ instantiation take care of the rest.

The documentation is rather less than explicit on the question of the lifetime of an IHTMLEditDesigner connection, so I've assumed that it lasts just as long as the currently loaded HTML document (probably a reasonable assumption given that we're attaching our Edit Designer to a HTML Document object). So I attach the Edit Designer in the views OnDownloadComplete() event. The code looks like this. <pre lang=c++> void CMyHTMLEditView::OnDownloadComplete(LPCTSTR lpszURL) { // other code that's irrelevant to this discussion . . . CHtmlEditView::OnDownloadComplete(lpszURL); m_pDoc = (IHTMLDocument2 *) GetHtmlDocument(); m_designer.Detach(); m_designer.Attach(m_pDoc); } m_pDoc is a pointer to the DOM for the current HTML page and m_designer is an embedded instance of CMSHTMLDisableDragHTMLEditDesigner in the view. Just to play safe I do a Detach() and then reattach m_designer to the IHTMLDocument2 interface. If the designer wasn't already attached to a document the detach does nothing - otherwise it removes itself from that documents list of IHTMLEditDesigner instances. This way I don't have to keep track of whether an Edit Designer has already been attached or not.

Attach() looks like this. <pre lang=c++> BOOL CMSHTMLDisableDragHTMLEditDesigner::Attach(IHTMLDocument2 *pDoc) { if (m_pServices != (IHTMLEditServices *) NULL) m_pServices->Release(); IServiceProvider *pTemp; if (pDoc == (IHTMLDocument2 *) NULL) return FALSE; pDoc->QueryInterface(IID_IServiceProvider, (void **) &pTemp); if (pTemp != (IServiceProvider *) NULL) { pTemp->QueryService(SID_SHTMLEditServices, IID_IHTMLEditServices, (void **) &m_pServices); if (m_pServices != (IHTMLEditServices *) NULL) { m_pServices->AddDesigner(this); return TRUE; } } return FALSE; } This queries the document COM object for an IServiceProvider interface from the document and then requests a IHTMLEditServices interface from the IServiceProvider interface. We then add ourselves, as a designer, to the IHTMLEditServices interface. If all of this succeeds MSHTML will call the various methods in our IHTMLEditServices derived object whenever anything interesting happens.

Detach() looks like this. <pre lang=c++> void CMSHTMLDisableDragHTMLEditDesigner::Detach() { if (m_pServices != (IHTMLEditServices *) NULL) m_pServices->RemoveDesigner(this); } The code within the PreHandleEvent() method might look like this. <pre lang=c++> CMSHTMLDisableDragHTMLEditDesigner::PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj) { if (inEvetDispId == DISPID_HTMLELEMENTEVENTS2_ONDRAG) pIEventObj->Cancel(); } given that the purpose of the class is to disable dragging. (The Cancel() function isn't really there, I'm trying to keep it simple for illustration). Looks good. We're confident that we've read and understood the docs so let's compile it and give it a run.

It doesn't bloody work!!! It doesn't crash but it certainly doesn't see a DISPID_HTMLELEMENTEVENTS2_ONDRAG event. Our end user can click on an image in our WYSIWYG HTML editor and move that image around until their arms fall off and there's nothing we can do about it!

So what went wrong?

Let's go back over what we've done to be sure we didn't miss anything. We implemented a class derived from IHTMLEditDesigner. We followed the MSDN documentation that tells us how to add our IHTMLEditDesigner object. And if we were to add a trace statement before the if test in CMSHTMLDisableDragHTMLEditDesigner::PreHandleEvent we'd see many many calls to the function. So what went wrong?

The documentation doesn't match the behaviour! I should state that this is what I've seen using Internet Explorer 6 with all current security updates applied, on Windows 2000 Service Pack 4 and all current hotfixes. The only notifications I see in my PreHandleEvent() are the raw mouse events, that is, DISPID_HTMLELEMENTEVENTS2_ONMOUSEDOWN, DISPID_HTMLELEMENTEVENTS2_ONMOUSEMOVE and DISPID_HTMLELEMENTEVENTS2_ONMOUSEUP.

Now if you've followed this far you've probably guessed that there is a solution to the problem. If there weren't I'd have probably posted a few questions on various message boards and given up.

The Solution

Lies in interpreting the data we're sent during a callback. In addition to the event identifier we get a pointer to an IHTMLEventObj interface which lets us query various things about the event. One of the things we can query is the srcElement which gives us a pointer to an IHTMLElement interface. If we look at that interface we see there's an onDragStart method which allows us to substitute an event handler which will be called when the user initiates a drag operation. We could set the event handler when we see a DISPID_HTMLELEMENTEVENTS2_ONMOUSEDOWN event.

The event handler

The event handler we provide to the onDragStart() method needs to be a IDispatch pointer with a default function that takes no parameters. I'd show you the class definition but it's literally just an IDispatch interface. Nothing special there.

CMSHTMLDisableDragDispatch::GetTypeInfoCount() returns 0, indicating there are no type information interfaces. CMSHTMLDisableDragDispatch::GetTypeInfo() returns DISP_E_BADINDEX no matter what parameters you pass and CMSHTMLDisableDragDispatch::GetIDsOfNames() returns DISP_E_UNKNOWNNAME whatever the requested ID's. So far it's a pretty minimal implementation of IDispatch. The real work (and the only purpose the class has) is in the Invoke() method. <pre lang=c++> HRESULT STDMETHODCALLTYPE CMSHTMLDisableDragDispatch::Invoke( DISPID /*dispIdMember*/, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS * /*pDispParams*/, VARIANT *pVarResult, EXCEPINFO * /*pExcepInfo*/, UINT * /*puArgErr*/) { // If we were installed it means we should disable // dragging. So set the return value to false pVarResult->vt = VT_BOOL; pVarResult->boolVal = false; return S_FALSE; } Even though there are a bunch of parameters passed to the function the only one we're interested in is the pVarResult one. Invoke() sets it to a false boolean and returns. Setting it to false cancels the event. Bingo, dragging is disabled!

Installing the event handler

is done in the IHTMLEditDesigner::PreHandleEvent() function thusly. <pre lang=c++> HRESULT STDMETHODCALLTYPE CMSHTMLDisableDragHTMLEditDesigner::PreHandleEvent( DISPID inEvtDispId, IHTMLEventObj *pIEventObj) { USES_CONVERSION; IHTMLElement *pSel; BSTR b = BSTR(NULL); if (inEvtDispId == DISPID_HTMLELEMENTEVENTS2_ONMOUSEDOWN) { if (pIEventObj != (IHTMLEventObj *) NULL) { pIEventObj->get_srcElement(&pSel); // We've got our source element, get its tag if (pSel != (IHTMLElement *) NULL) { pSel->get_tagName(&b); if (_tcsicmp(_T("IMG"), W2A(b)) == 0) { // We only install the ondragstart handler if the // element is an IMG. VARIANT v; v.vt = VT_DISPATCH; v.pdispVal = &m_dp; pSel->put_ondragstart(v); } } } } return S_FALSE; } m_dp is an instance of CMSHTMLDisableDragDispatch embedded in our Edit Designer.

You'll notice there's a test on the elements Tag property. That's because I only wanted to disable dragging of images. If we install the event handler on any srcElement we disable dragging for all objects on the page. By checking for an IMG tag we ensure that we're installing the event handler only for image objects.

You'll notice the demo project uses CodeProject as the HTML document (what else would it use?). You'll also notice that some images are still dragable. Bob for instance. That's because Bob's tag is AREA not IMG. You can see where this is going...

Using the code

To use the Edit Designer in your own projects you need to add the source files in the source download. Then, in your edit view you add two data members <pre lang=c++> IHTMLDocument2 *m_pDoc; CMSHTMLDisableDragHTMLEditDesigner m_designer; Add an OnDownloadComplete() function to your view (the wizard can do this for you) and add the following lines in the function <pre lang=c++> m_pDoc = (IHTMLDocument2 *) GetHtmlDocument(); m_designer.Detach(); m_designer.Attach(m_pDoc); Voila, you're done!


28 March 2004 - Initial version.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Rob Manderson
United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.

I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.

Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.

Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.

I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.

Oh, I'm also a Kentucky Colonel.

You may also be interested in...


Comments and Discussions

AnswerRe: HTML-Editing with VC6? Pin
Rob Manderson29-Mar-04 6:18
editorRob Manderson29-Mar-04 6:18 
GeneralRe: HTML-Editing with VC6? Pin
Neville Franks29-Mar-04 10:49
memberNeville Franks29-Mar-04 10:49 
GeneralRe: HTML-Editing with VC6? Pin
Rob Manderson29-Mar-04 16:21
editorRob Manderson29-Mar-04 16:21 
GeneralRe: HTML-Editing with VC6? Pin
Uwe Keim30-Mar-04 18:33
sitebuilderUwe Keim30-Mar-04 18:33 
AnswerRe: HTML-Editing with VC6? Pin
Jean-Michel LE FOL2-Apr-04 8:50
memberJean-Michel LE FOL2-Apr-04 8:50 
GeneralEdit designers for advanced table editing Pin
Uwe Keim28-Mar-04 21:34
sitebuilderUwe Keim28-Mar-04 21:34 
GeneralRe: Edit designers for advanced table editing Pin
Rob Manderson28-Mar-04 21:55
editorRob Manderson28-Mar-04 21:55 
GeneralRe: Edit designers for advanced table editing Pin
Uwe Keim28-Mar-04 22:00
sitebuilderUwe Keim28-Mar-04 22:00 
Yes, I fear with the next version of our CMS [^], I do have to implement THAT functionality, too... Frown | :-( Smile | :)

Until now, I managed to get around it Big Grin | :-D

- Free Windows-based CMS:
- See me:
- MSN Messenger:

GeneralRe: Edit designers for advanced table editing Pin
Rob Manderson28-Mar-04 22:02
editorRob Manderson28-Mar-04 22:02 
GeneralRe: Edit designers for advanced table editing Pin
basementman30-Mar-04 11:35
memberbasementman30-Mar-04 11:35 
GeneralRe: Edit designers for advanced table editing Pin
Uwe Keim30-Mar-04 17:57
sitebuilderUwe Keim30-Mar-04 17:57 
GeneralRe: Edit designers for advanced table editing (Visual) Pin
LiquidCrystal31-Mar-04 8:57
memberLiquidCrystal31-Mar-04 8:57 
GeneralRe: Edit designers for advanced table editing (Visual) Pin
Uwe Keim31-Mar-04 17:41
sitebuilderUwe Keim31-Mar-04 17:41 
GeneralAwesome Pin
Hockey28-Mar-04 21:34
memberHockey28-Mar-04 21:34 
GeneralRe: Awesome Pin
Rob Manderson28-Mar-04 21:54
editorRob Manderson28-Mar-04 21:54 
GeneralNice Job Pin
Nick Parker28-Mar-04 11:37
editorNick Parker28-Mar-04 11:37 
GeneralRe: Nice Job Pin
Rob Manderson28-Mar-04 11:40
editorRob Manderson28-Mar-04 11:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170915.1 | Last Updated 25 Apr 2004
Article Copyright 2004 by Rob Manderson
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid