Writing a Managed Internet Explorer Extension: Part 2.5

When we last discussed wiring events in part 2, we discussed how events work and how to wire them, and more importantly how to unwire them. I also mentioned that we could use attachEvent and detachEvent rather than the events on interfaces. This is useful if you don’t know what type of element you are attaching an event to.

attachEvent and detachEvent

attachEvent is part of the IHTMLElement2 interface, and fortunately all elements and tags implement this interface, so long as you are targeting Internet Explorer 5.0+. attachEvent takes two parameters, a string indicating which event to attach to, and the actual handler itself. It’s signature looks like this:

attachEvent(
BSTR event, //in
IDispatch *pDisp, //in
VARIANT_BOOL *pfResult //out, retval
);

The IDispatch is the event handler. Unfortunately this means it isn’t as simple as passing a delegate to it and “it just works”. We need to implement a class that will marshal the handler to COM correctly.

Also note, that like Part 2, we need to call detachEvent to stop from memory leaking. Rather than implement the IDispatch interface ourselves, we can use the IReflect interface to give COM marshalling all of the help that it needs. When IDispatch (in this case) invokes the event, it will use the name “[DISPID=0]”. We can handle that in the InvokeMember implementation of IReflect, otherwise we just pass it to our own type. The nice approach to this is it uses a common delegate that we are probably already fimiliar with, EventHandler. In this case, I’ve called the class EventProxy.

public class EventProxy : IReflect
{
private readonly string _eventName;
private readonly IHTMLElement2 _target;
private readonly Action<CEventObj> _eventHandler;
private readonly Type _type;

public EventProxy(string eventName, IHTMLElement2 target, Action<CEventObj> eventHandler)
{
_eventName = eventName;
_target = target;
_eventHandler = eventHandler;
_type = typeof(EventProxy);
}

public IHTMLElement2 Target
{
get { return _target; }
}

public string EventName
{
get { return _eventName; }
}

public void OnHtmlEvent(object o)
{
InvokeClrEvent((CEventObj)o);
}

private void InvokeClrEvent(CEventObj o)
{
if (_eventHandler != null)
{
_eventHandler(o);
}
}

public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers)
{
return _type.GetMethod(name, bindingAttr, binder, types, modifiers);
}

public MethodInfo GetMethod(string name, BindingFlags bindingAttr)
{
return _type.GetMethod(name, bindingAttr);
}

public MethodInfo[] GetMethods(BindingFlags bindingAttr)
{
return _type.GetMethods(bindingAttr);
}

public FieldInfo GetField(string name, BindingFlags bindingAttr)
{
return _type.GetField(name, bindingAttr);
}

public FieldInfo[] GetFields(BindingFlags bindingAttr)
{
return _type.GetFields(bindingAttr);
}

public PropertyInfo GetProperty(string name, BindingFlags bindingAttr)
{
return _type.GetProperty(name, bindingAttr);
}

public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
{
return _type.GetProperty(name, bindingAttr, binder, returnType, types, modifiers);
}

public PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
return _type.GetProperties(bindingAttr);
}

public MemberInfo[] GetMember(string name, BindingFlags bindingAttr)
{
return _type.GetMember(name, bindingAttr);
}

public MemberInfo[] GetMembers(BindingFlags bindingAttr)
{
return _type.GetMembers(bindingAttr);
}

public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
{
if (name == "[DISPID=0]")
{
OnHtmlEvent(args == null ? null : args.Length == 0 ? null : args[0]);
return null;
}
return _type.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
}

public Type UnderlyingSystemType
{
get { return _type.UnderlyingSystemType; }
}
}

Instances of EventProxy can be handed as the IDispatch to attachEvent. We would use it like so:

private void _webBrowser2Events_DocumentComplete(object pdisp, ref object url)
{
var document = (HTMLDocument)_webBrowser2.Document;
var htmlElements = document.getElementsByTagName("input").OfType<IHTMLElement2>();
foreach (var htmlElement in htmlElements)
{
Action<CEventObj> handler = s => MessageBox.Show("Clicked: " + s.srcElement.id);
var proxy = new EventProxy("onclick", htmlElement, handler);
htmlElement.attachEvent("onclick", proxy);
}
}

Like in part 2, we need to keep a running dictionary of all of the events that we attach, then call detachEvent on BeforeNavigate2. I’ll leave that to you, but at the end of the series I will post the entire working solution in a VS 2010 project.

DocumentComplete Fired Multiple Times

If you use the code from above or from part two, you may notice that clicking an input element causes a dialog to show several times. That is because DocumentComplete is being called more than once. DocumentComplete is fired whenever any document completes, not just the whole thing. So content from an <iframe> will cause the DocumentComplete to get fired again. Sometimes this behavior is desirable, but in this case it is not. How do we ensure it’s only called once for the main document?

The DocumentComplete gives use two things: the URN of the document that was loaded, and a pointer to a dispatch object. Simply put, if the pdisp is the same reference as your IWebBrowser2 instance you setup from SetSite, then it’s the “root” document. This ensures DocumentComplete is only fired once, when the main document is complete:

   1: if (!ReferenceEquals(pdisp, _webBrowser2))


   2: {


   3:     return;


   4: }







As I mentioned in part 2, part 3 will be about manipulating the DOM. I just wanted to cover this first.

One thought on “Writing a Managed Internet Explorer Extension: Part 2.5”

  1. Hi,

    Nice postings about ie mgt extensions. Hard to find these kinds of postings.. keep it up..

    one questions: we’re trying to find (using mgt ext or using java script/jquery) how to get an event notification for when a request has been made by the browser.

    Other words, we need a global handler which notifies just before a request (ajax or regular) is (about) to be sent to the server.

    Thanks,

    Gopi

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>