25
Nov
2008
quinox

Triggering DocumentCompleted after the OnLoad event

Programming in C# with an IDE like Visual Studio is a breeze, and using the WebBrowser Control from Dot Net is not bad either... until you discover the DocumentCompleted event gets triggered before the Javascript's OnLoad gets executed, and you like to have this order reversed.

At first I tried to make my program execute code after the OnLoad events by using the Timer class. In the DocumentCompleted event handler I placed a timer, which would run after 500ms. This in itself works just fine, but as soon as you try to interact with the GUI of your program you'll get exceptions thrown in your face talking about invalid cross-thread operations. By design the timer starts a new thread, and from that thread you cannot meddle with the GUI because it runs in a different thread and you could corrupt it otherwise.

The next solution was to use a delegate with a callback as described in the article How to: Make Thread-Safe Calls to Windows Forms Controls. At first I placed these inside the GUI code which worked for a number of function calls, but it polluted this code enormously and in the end proved to be ineffective to stop the exceptions. I then tried to place these delegates in my library code, but this proved to be impossible because it is its own class, not of a type Windows Form so the whole Invoke() was missing.

Eventually I figured I'd play dirty, and use the original timer idea and the WebBrowser events to get back to the original thread. It's nasty code, but it works like a charm so I'll stick with it for now.

// Globals (don't use magic Magic Numbers)
LOADDELAY = 500;
// Hook the DocumentCompleted event as normal
myWebBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);

private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (e.Url.ToString().StartsWith("about:delayed:"))
{
// We just waited for the OnLoad to finish
// put your normal code here
}
else
{
// Using a timer to "sleep" while the OnLoad can be processed
new System.Threading.Timer(LoadDelayed, e, LOADDELAY, System.Threading.Timeout.Infinite);
}
}
// This will trigger the browser_DocumentCompleted event for the second time,
// with a modified Uri so you can recognize it.
private void LoadDelayed(object _e)
{
WebBrowserDocumentCompletedEventArgs e = (WebBrowserDocumentCompletedEventArgs) _e;
WebBrowserDocumentCompletedEventArgs f = new WebBrowserDocumentCompletedEventArgs(new Uri("about:delayed:" + e.Url.ToString()));
WebBrowserDocumentCompletedEventHandler d = new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
myWebBrowser.Invoke(d, new object[]{ myWebBrowser, f});
}