Handling DOM events in a C# ActiveX
I recently had to implement an ActiveX object (or I guess just a COM object since it had no UI) in C#. There are several articles out there about how to do this (bing it), but I couldn’t find much information about handling DOM events in managed code. After some digging and experimentation I found a way to make this work, which is the subject of this post. I’ll assume you have some familiarity with getting a basic managed object running in IE and calling its methods from Javascript.
The first step in handling a DOM event is getting a pointer to the IHtmlDocument2 interface in your C# code. The easiest way is to just pass it in explicitly from Javascript. To do this first you’ll need to add a COM reference to mshtml:
Then you add a method to your COM object to receive this pointer (I decided to call it Initialize):
using System;
using System.Runtime.InteropServices;
using mshtml;
namespace ManagedActiveX
{
public interface ITestObject
{
void Initialize(IHTMLDocument2 doc);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class TestObject : ITestObject
{
public void Initialize(IHTMLDocument2 doc)
{
}
}
}
And you can call this from your Javascript code to pass in the document:
<script language="javascript" type="text/javascript">
var testObj = new ActiveXObject("ManagedActiveX.TestObject");
function Init() {
testObj.Initialize(document);
}
</script>
At this point your C# object has a pointer to the document and can get to the various events the DOM exposes, but how do you register a callback? Let’s take window.onunload for example:
public void Initialize(IHTMLDocument2 doc)
{
doc.parentWindow.onunload = ??
}
The onunload member is of type object, which doesn’t give much of a clue about what needs to go in there. I tried a delegate but that didn’t work. Then I looked at the documentation for this member which describes what you need to pass as:
VARIANT of type VT_DISPATCH that specifies the IDispatch interface of an object with a default method that is invoked when the event occurs.
So how do you do that from C#? After doing a little reading I found all COMVisible managed objects automatically implement IDispatch. That is, the COM Callable Wrapper generated by .NET to expose your object to COM creates an implementation of IDispatch for you. Now we need to figure out how to tell the wrapper which method is the default IDispatch method. I found you specify this by marking the method with a Dispatch Id of 0:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class OnUnloadHandler
{
IHTMLDocument2 doc;
public OnUnloadHandler(IHTMLDocument2 document)
{
doc = document;
}
[DispId(0)]
public object DefaultMethod()
{
doc.parentWindow.alert("onunload event handled in managed code");
return null;
}
}
Then you simply create an instance of this type and set it as the onunload handler:
public void Initialize(IHTMLDocument2 doc)
{
doc.parentWindow.onunload = new OnUnloadHandler(doc);
}
If you host this object on a page with the script code shown above and then navigate away from it you should get the alert to pop up. Drop me a line if this doesn’t (or does :)) work for you.
Comments
- Anonymous
November 30, 2010
Hi, thank you for this topic - it's very useful for me. But I have one question, maybe you can help with it too.Is it possible to listen for DOM events without passing document from javascript? I want to listen to SELECT dom event to get notified in C# code, when user selects text on any webpage. Maybe its possible to get document through WinAPI calls or etc (at least from IE)?