ClydeD

I'm writing a HTML editor application in C++/CLI based on IE7.0 using CHTMLEditView as an MFC base class. I'm a newbie to this technology although an old hand at programming. I'm trying to detect changes in the main document after I've set contentEditable=TRUE on several attributes and typed into them. To do this, I created a class that implemented IHTMLChangeSink and bound an object of the class as follows from the OnDocumentComplete override of HTMLEditView. The problem is that although the call to CreateChangeLog seems to work, nothing I type fires Notify. The really bizarre aspect to all of this is that when I originally tried to implement this in VB.NET using a managed class, it all worked!

Also, is there any documentation on how to use IHTMLChangeLog so Undo events can be trapped and intercepted The existing MSHTML documentation is so sparse. Any chance of opening this up to Community Content so if MS people don't have time to plug the gaps, others can

HRESULT hr;

if (m_pMarkupContainer2)

{

READYSTATE state = GetReadyState();

if (state==READYSTATE_COMPLETE)

{

CNativeChangeSink* mySink = new CNativeChangeSink(this);

hr = m_pMarkupContainer2->CreateChangeLog((IHTMLChangeSink*)mySink, &m_pChangeLog, TRUE, TRUE);

ASSERT(SUCCEEDED(hr));

IHTMLChangeSink* tempSink;

mySink->QueryInterface(IID_IHTMLChangeSink, (void**) &tempSink);

hr=m_pMarkupContainer2->RegisterForDirtyRange(tempSink, &mpdwRangeCookie);

tempSink->Release();

ASSERT(SUCCEEDED(hr));

}

}



Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

ClydeD

So nobody, not even working in Microsoft, knows anything about how to hook up these MSHTML interfaces Doesn't surprise me, really.



Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

eugene.z

Saw reference to that post at Tim Anderson's blog...

By chance, I have been using IChangeSink in the past, and as far as I remember did not have any problems. I used it to track when document is changed; apart from couple of unexplained calls to Notify (hwne design mode is activated, for example), it works as one would expect.

The ATL control I used implemented IChangeSink interface:

class ATL_NO_VTABLE CHTMLCustomInputUI :

    public IHTMLCustomInputUI,

    public IHTMLEditDesigner,

    public IHTMLChangeSink,

    public IDispEventSimpleImpl<0,CHTMLCustomInputUI,&DIID_DWebBrowserEvents2>,

{

public:

    BEGIN_SINK_MAP(CHTMLCustomInputUI)

        SINK_ENTRY_INFO  (0,DIID_DWebBrowserEvents2,DISPID_DOCUMENTCOMPLETE,OnDocumentComplete,&afiDocumentComplete)

     END_SINK_MAP()

public:

    void _stdcall OnDocumentComplete(IDispatch* pWinDisp,VARIANT* vt); // invoked on document load complete

    HRESULT STDMETHODCALLTYPE Notify();

...

}

Now, the only thing to do is to make sure that Notify method is called:

HRESULT    CHTMLCustomInputUI::AttachChangeSink()

{

    // get current document pointer from somewhere ...

    CComPtr<IHTMLDocument2> pCurrentDocument;

    CComPtr<IMarkupContainer2> pMarkupCont;

    HRESULT hr = -1;

    // get document mark up container

    pMarkupCont = pCurrentDocument;

    if (pMarkupCont == NULL)

        return hr;

    // create changes log; the sink will be attached for change notifications

    hr = pMarkupCont->CreateChangeLog((IHTMLChangeSink*)this,&m_pLog,TRUE,FALSE);

    return hr;

}

 

HRESULT CHTMLCustomInputUI::Notify()

{

    m_bDocumentChanged = true;

    return S_OK;

}

And the sink was attached in  on document complete event:

void _stdcall CHTMLCustomInputUI::OnDocumentComplete(IDispatch* pWinDisp,VARIANT* vt)

{

    // after the document is loaded

    // it is not dirty yet

    m_bDocumentChanged = false;

        // attach to change sink - need to do after every load

    attachChangeSink();

}

Hope that helps. Let me know if it does not,  I will dig some more (it is an old project - something like 4 years old).

Regards, Eugene

P.S. Sorry, did not see that you are using MFC. Mine was ATL (and if you ask me, I would use ATL once again - you will need to tweak things, and in ATL it is easier).

 

 






Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

Tim Anderson

By chance, I have been using IChangeSink in the past, and as far as I remember did not have any problems. I used it to track when document is changed; apart from couple of unexplained calls to Notify (hwne design mode is activated, for example), it works as one would expect.

Did you use the actual Change Log, or just Notify Notify itself works fine, I agree (presuming it fires OK). It is the rest of it I find somewhat perplexing.

Tim





Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

eugene.z

Tim,

If I remember correctly (do not have code handy right now), I used ExecCommand (or whatever it is called) to implement Undo/Redo for the editor control I was developing.

On the whole, the object model is rather quirky, so by the time I got there I was afraid to take on something as poorly described and somewhat complex as change log :).

By the way, I also found out that Notify is called in couple of cases where it should not (even when it fires). Had bugs because of that.

Regards, Eugene






Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

ClydeD

Tim, Eugene

Thanks for your posts here. Seeing as it was my travails that prompted this entire discussion and Tim's subsequent blog posting, I ought to come in with my subsequent observations.

As far as I can tell, Eugene, what I've done is more or less what you have done, albeit in MFC . My implementation of my change sink is a simple one without any macros. As far as I can tell it ought to work: this kind of implementation works fine for my IHTMLEditDesigner, so why not this simple interface

I suspect it's a threading issue. I don't think that MFC uses a STA model, but IE expects everything to be STA so it's not marshalling the pointers at its end. But,. if that's the case, why doesn't IHTMLEditDesigner fail

Clyde

PS: code below. Please tell me if you see any howlers...

#pragma once

#include "mshtml.h"

class CGDCPPView;

class CNativeChangeSink :

public IHTMLChangeSink

{

public:

CNativeChangeSink(void);

CNativeChangeSink(CGDCPPView* theView);

public:

~CNativeChangeSink(void);

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 Notify(void);

private:

UINT m_uRefCount;

CGDCPPView* m_pTheView;

};

and the implementation is:

#include "StdAfx.h"

#include "NativeChangeSink.h"

#include "GDCPPView.h"

CNativeChangeSink::CNativeChangeSink(void): m_uRefCount(0U)

{

}

CNativeChangeSink::CNativeChangeSink(CGDCPPView* theView):m_uRefCount(0U)

{

m_pTheView=theView;

}

CNativeChangeSink::~CNativeChangeSink(void)

{

}

HRESULT STDMETHODCALLTYPE CNativeChangeSink::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject)

{

HRESULT hrRet = S_OK;

// Standard QI() initialization - set *ppv to NULL.

*ppvObject = NULL;

// If the client is requesting an interface we support, set *ppvObject.

if (IsEqualIID(riid, IID_IUnknown))

*ppvObject = (IUnknown *) this;

else if (IsEqualIID(riid, IID_IHTMLChangeSink))

*ppvObject = (IHTMLChangeSink*) this;

else

// We don't support the interface the client is asking for.

hrRet = E_NOINTERFACE;

// If we're returning an interface pointer, AddRef() it.

if (S_OK == hrRet)

((IUnknown *) *ppvObject)->AddRef();

return hrRet;

}

ULONG STDMETHODCALLTYPE CNativeChangeSink::AddRef(void)

{

return ++m_uRefCount;

}

ULONG STDMETHODCALLTYPE CNativeChangeSink::Release(void)

{

return --m_uRefCount;

}

HRESULT STDMETHODCALLTYPE CNativeChangeSink::Notify(void)

{

m_pTheView->GetDocument()->SetModifiedFlag(TRUE);

System::Diagnostics::Trace::WriteLine(L"Notify called");

return S_OK;

}





Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

Tim Anderson

If I remember correctly (do not have code handy right now), I used ExecCommand (or whatever it is called) to implement Undo/Redo

Yes, that is the easy way. I wonder if anyone has managed to use the change log

Tim





Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

eugene.z

Clyde,

The code looks simple enough. Only thing that I suspect is the reference counting mechanism - why do you implement it I am not a big expert in MFC, but surely there is some class you can inherit from, implementing reference counting logic And in you original code snippet you query for and right after release the interface; at that stage the reference count would be zero (because you do not increment it upon creation of the object). Could that be an issue - that the class is released, and therefore the method is not called

Those are my only thoughts on the matter.

Cheers, Eugene






Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

ClydeD

Hi Eugene

Thanks for the useful comments. I Implemented the reference counting mechanism so I could see whether MSHTML was explicitly trying to attach to the interface. if I set a breakpoint and step through the code then MSHTML calls QueryInterface when I call CreateChangeLog but does not release, so the reference count tells me that the object is active because it's >0.

FAO Internet Explorer team: if you are going to document interfaces then either do it properly or not at all. Don't lead developers 'up the garden path' and into believing that they can accomplish things though mechanisms that, and the end of the day, you really aren't committed to supporting properly. And, for heaven's sake, engage with issues in the community rather than turning a blind eye to them! Neither tactic is at all defensible.





Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

ClydeD

At times like these, I really wish there was a <tumbleweed> smiley...



Re: Internet Explorer Extension Development Can't get IHTMLChangeSink::Notify to fire

Vadim Yakovlev

Clyde, I'm not sure if it can help you, but I also run into a problem with IHTMLChangeSink::Notify not firing. Instead of using contentEditable attribute, I was putting entire browser into design mode by setting it's IHTMLDocument2:: designMode property to 'On'. Sometimes Notify was firing, sometimes not. After some digging, I've found that it is necessary to query MSHTML object for IMarkupContainer2 AFTER setting design mode, not before. Then it works.

That is, this code works, and Sink.Notify is called (this is Borland Pascal, I hope it's clear enough):

Code Snippet

var

Browser: IHTMLDocument2;

Cookie: DWORD;

Sink: IHTMLChangeSink;

....

Browser.designMode := 'On';

(Browser as IMarkupContainer2).RegisterForDirtyRange(Sink,Cookie);

And this code doesn't:

Code Snippet

var

Browser: IHTMLDocument2;

Cookie: DWORD;

Sink: IHTMLChangeSink;

....

(Browser as IMarkupContainer2).RegisterForDirtyRange(Sink,Cookie);

Browser.designMode := 'On';

In fact, MSHTML returns different interface pointers for IMarkupContainer2 before and after switching from browser mode to design mode.

Code Snippet

var

MC1, MC2: IMarkupContainer2;

Browser: IHTMLDocument2;

....

MC1 := Browser as IMarkupContainer2;

Browser.designMode := 'On';

MC2 := Browser as IMarkupContainer2;

After executing this code, MC1 and MC2 are different. As a side effect of this behaviour, after switching browser from design mode to browser mode and then back into design mode, you need to register your IHTMLChangeSink pointer again, and you can't use stored IMarkupContainer2 pointer for that, you need to request a new one.