Preface
I am not a native English speaker, and I am not heading for the Pulitzer Prize of Journalism. So, if anyone fine this “article” a bit short, get patient. My purpose here is to provide the most useful information with not too much talking. Besides, my C# English is fine enough for the VS2005 compiler :-). I hope the readers of this article will get their answer to the subject they Googled for: "Handling HTML events in .NET".
Background
I was looking for a way to handle HTML events with the .NET EventHandler
. While writing my Password Management Toolbar, I needed a way to hook to the “onsubmit
” event of the HTML document form in order to check the login information.
Anyway, I looked for forums and threads that would give me a straight answer, but didn’t find any. So, that left me no choice but to go to the last resort: the Reflector (Luts Roeder’s .NET Reflector).
What did I look for? When working with the WebBrowser
control, you get the HtmlElement
wrapper class that allows you to bind to mouse events pretty easily (like the HtmlElement.Click
event). But, I do not work with the WebBrowser
control, BandObject
(this is the toolbar object), or the Browser Helper Object (BHO) which only have access to the MSHTML DOM elements. Also, the managed wrapper do not provide an event for “onsubmit
”, so even the WebBrowser
control wouldn’t do for me. So, I started digging with the Reflector to see what the guys in Microsoft did to allow binding to mouse events. The main thing that I found was the HtmlToClrEventProxy
class which provided the correct interface for the object needed by the IHTMLElement2.attachEvent
method. Anyway, this class is internal, so I took most of it, refined it, and made a similar public version of it that allows .NET users to easily attach to any HTMLElement
event.
Using the code
The best way is to download and try the sample first. There is only one class you need to add to your project, this is the HtmlEventProxy
class. This class have a factory method Create()
which will do all the binding. Then, when you want to detach from the event, simply call the Detach()
method of the object. The “sender
” object in the EventHandler
is the proxy itself. In order to get the underlying HTMLElement
, use the HtmlElement
property. Here are a few of the code snippets from the sample:
Let's start from the .NET event handler of the onsubmit
form event. It has the regular signature of the EventHandler
. The code here also extracts the form element from the sender and shows its outerHTML
string.
I chose to detach from the event once it is consumed, using the Detach()
method.
private void FormSubmitHabdler(object sender, EventArgs e)
{
MessageBox.Show("form submitted");
IHTMLElement form =
((HtmlEventProxy)sender).HTMLElement as IHTMLElement;
MessageBox.Show("outer html:" + form.outerHTML);
((HtmlEventProxy)sender).Detach();
}
Now, for the code that binds the form onsubmit
event to the handler:
object form = webBrowser1.Document.Forms[0].DomElement ;
HtmlEventProxy.Create("onsubmit", form, FormSubmitHabdler);
object button = webBrowser1.Document.GetElementById("button1").DomElement;
HtmlEventProxy.Create("onclick", button, ButtonClickHabdler);
First, obtain the element object from the document. Then, create a proxy with the name of the event, the DOM element, and the .NET handler.
The “HtmlEventProxy” class
The class implements the IReflect
interface; that is the interface that is needed by IHTMLElement2.attachEvent(string ev,object pDisp)
. The most important is the implementation of the IReflect.InvokeMember
method. This method detects the call to the first method entry and executes the .NET handler.
I also implemented the IDisposable
interface, so the event is detached when the proxy is disposed.
public class HtmlEventProxy : IDisposable,IReflect
{
private object sender;
private IReflect typeIReflectImplementation;
private IHTMLElement2 htmlElement = null;
private string eventName = null;
private HtmlEventProxy(string eventName, IHTMLElement2 htmlElement,
EventHandler eventHandler)
{
this.eventName = eventName;
this.htmlElement = htmlElement;
this.sender = this;
this.eventHandler = eventHandler;
Type type = typeof(HtmlEventProxy);
this.typeIReflectImplementation = type;
}
public static HtmlEventProxy Create(string eventName,
object htmlElement, EventHandler eventHandler)
{
IHTMLElement2 elem = (IHTMLElement2)htmlElement;
HtmlEventProxy newProxy = new HtmlEventProxy(eventName,elem, eventHandler);
elem.attachEvent(eventName, newProxy); return newProxy;
}
public void Detach()
{
lock (this)
{
if (this.htmlElement != null)
{
IHTMLElement2 elem = (IHTMLElement2)htmlElement;
elem.detachEvent(this.eventName, this);
this.htmlElement = null;
}
}
}
public IHTMLElement2 HTMLElement
{
get
{
return this.htmlElement;
}
}
#region IReflect
......
object IReflect.InvokeMember(string name,BindingFlags invokeAttr, Binder binder,
object target, object[] args, ParameterModifier[] modifiers,
CultureInfo culture, string[] namedParameters)
{
if (name == "[DISPID=0]")
{
if (this.eventHandler != null)
{
this.eventHandler(this.sender, EventArgs.Empty);
}
}
return null;
}
#endregion
}
You can download the latest version of HtmlEventProxy.cs from here.
Enjoy!