Thursday, July 26, 2007

Working With The WebBrowser Control in WinForms

The WebBrowser control is a handy widget because you can use it to display complex content rendered in HTML in your Windows Forms applications.  The WebBrowser control, like a number of other things in .NET, is simply a wrapper around a COM control.  Additional features can be pulled from that underlying COM control by writing your own feature wrappers; however, I was able to get my required functionality straight from the .NET wrapper.

Documentation’s not all that clear on the WebBrowser, and a lot of examples actually are written for the simplistic navigation model where you’re merely programatically pointing at existing web-based URLs or files on a filesystem.  Things get a little trickier when you need to work with streamed content of any form, which is of course what I’ve been doing…

The WebBrowser holds content to render in an System.Windows.Forms.HtmlDocument object.  If you’re navigating to web or file-based content then the control manages the lifecycle of that object for you.  Life is easy.  If you’re working with some sort of stream or text data that you want to load directly into the HtmlDocument, then you’ll need to first have the control load up an empty HtmlDocument for you.  You’ll then make use of the HtmlDocument’s Text, InnerXml, or OuterXml properties to load that content.  The code below walks through this sort of case.

NOTE:  The following snippets are transmorgified from my exisiting solution and aren’t necessarily complete or compilable.  For clarity’s sake the snippets are also fuglyware, meaning there’s no error checking or good design principles.  Do the right thing and make your own code smell-free.

First, create the control:

WebBrowser browser = new WebBrowser();

Now navigate off to the “about:blank” URL which will cause a new, empty HtmlDocument to get loaded in the browser:

browser.Navigate("about:blank");

Document loading in the WebBrowser is asynchronous.  You’ll need to wait around for that document to complete its loading before you proceed on.  There are a couple different ways you can handle that.  The WebBrowser.StatusText will give you clues about the Document’s status; however, the preferred way is to handle the DocumentComplete handler.  I use a simple boolean flag and wait on it being toggled by the handler:

private bool canProceedWithNextDocLoad = false;

 

private void contentPane_DocumentCompleted(object sender,

                                           WebBrowserDocumentCompletedEventArgs e)

{

    canProceedWithNextDocLoad = true;

}

A helper method is used to wait for the document to load:

private void WaitOnDocumentLoad()

{

    //canProceedWithNextDocLoad is set true in DocumentCompleted

    while (! canProceedWithNextDocLoad)

    {

        Application.DoEvents();

    }

    canProceedWithNextDocLoad = false;

}

Actual navigation flows like this: get some content from your data source, load the content into the Text/InnerXml/OuterXml property, and immediately wait for the document to finish loading.  Those steps together looks something akin to this:

string htmlContent = ContentAgent.GetContent("someTargetToGetContentFor"");

browser.DocumentText = htmlContent;

WaitOnDocumentLoad();

The ContentAgent.GetContent call is simply how we’re handling our data access.  The real working parts are actually more complicated because I’m getting XML content from the database and running it through an XML Transform using DocBook stylesheets.  I’ve blogged elsewhere about that, so I just simplified some stuff here for clarity’s sake.

I’m working straight with the DocumentText string property.  You can also make use of the InnerXml or OuterXml and alter parts of the HtmlDocument’s Document Object Model directly.  It was easier for me to just deal directly with the entire contents.

There’s a lot of other complexity to be considered here, and there are a number of other features you can tack on.  Navigation in this manner loses the history list, so you’ll need to implement something yourself if you want to track history.  I use a simple queue to handle that.  Another thing is that all the regular navigation buttons such as forward, backward, stop, refresh, and home aren’t available, so you’ll need to work out the details on those bits of functionality — but it’s not difficult once you’ve got the basic navigation moves down.

So there you have it.  Jim’s version of Cliff Notes on the WebBrowser control.

4 comments:

Anonymous said...

thank you!

Bjornabee said...

hey hey, i was trying for ages to have a robust document complete event. If the doc has frames then as you probabally know it fires a hell of a lot.

anyway, pseudo code

bool isDocReady
isDocReady=false
webbrowser.navigate(url)

webbrowser->documentComplete
{
bool _isDocReady = true
List webbrowserDocs
webbrowserDocs.add(webbrowser.document)

foreach webbrowserDoc in webbrowserDocs
{
if webbrowserDoc.readyState <> "Complete" Then _isDocReady = false;
foreach frame in webbrowserDoc
{
webbrowserDocs.add(frame)
}
}
isDocReady = _isDocReady
}

Unknown said...

Hi, Having a beautiful website no one can find is like having a store and keeping the doors locked in Web Design Cochin. You know it is there, you've done a great job decorating it, the products are waiting for the customers, yet no one comes in.Thanks.....

Anonymous said...

Windows Forms UI controls

Subscribe (RSS)

The Leadership Journey