Wednesday, September 03, 2008

Enriching .NET Windows Apps with the WebBrowser control

I am currently working on a desktop product upgrade project.  The old versions were developed on the .NET 1.1 platform.  They utilized a web browser control to display information for printing.  Unfortunately the support for hosting a web browser control in a windows form was poor so the original development team had to create their own control with hooks into the Internet Explorer objects and such to do it.  Among other changes, we are doing a complete re-write of this application in .NET 2.0.  (We still need to support some older platforms, so 3.0 wasn't an option.)

One of the requirements of the new version dictated that we need to do some more intricate display of the information.  The prime display control candidate for this information is a "tree-grid" hybrid.  The standard .NET 2.0 toolbox simply doesn't have a control that can handle what we need (a shame really).  There are many third party controls that could do this, but that introduced a learning curve that our project timeline simply wouldn't support.  Obviously, there is additional cost involved with such a control as well.  As I evaluated what we needed to achieve, and being a web developer, I naturally looked towards HTML as a solution.  The desired output could be executed very simply with standard HTML constructs.

While analyzing the current application architecture and scoping out how we were going to integrate the changes, we decided to change the data storage strategy as well.  The application uses a local MS-Access database with a small set of tables to store standard relational data.  Given that we really don't need relational access to the ancillary tables of the data model, we determined that we could simplify the whole thing greatly by reducing the data architecture to a single table with a blob of XML to represent the bulk of the record's detail.

Now that we have serialization going on for database storage, the natural step was to use that XML for display.  One of the great additions made in fx 2.0 is the System.Windows.Forms.WebBrowser control class.  This provides simple, native hosting of a web browser inside a windows form.  To solve the complex display problem, we placed a web browser control on the main user control, serialize the data model object instance to XML, apply an XSL transformation to it then feed the result directly to the web browser control in the form.  The browser control has the property "DocumentText" which you can write HTML directly to.  Elegant, simple and surprisingly fast.  A natural side effect of this strategy is that it becomes trivial to change the view of the data: simply develop a different style sheet and provide a switching mechanism.

While generating XML, transforming it to HTML and displaying it on the form was now very easy with the browser control, the big question was, how do we interact with it.  

In order for the browser to interact with the user control, it must be exposed to COM by being decorated with the COMVisibleAttribute class.

public partial class OurUserControl : UserControl

This exposes the object to COM and thus allows the COM based browser to see and interact with it.  Now we need to tell the browser what to interact with.  The browser control has the property "ObjectForScripting".  You give this property any object in your windows application context.  In our case, when the user control is created, we hand the browser the actual control instance.

public OurUserControl()
webBrowser1.ObjectForScripting = this;

This exposes the object to the web browser's window as "window.external".

In order for the browser context (i.e. the HTML DOM) to call the methods on the win form context scripting object (the windows user control), we need to make some methods visible.  This is simply a matter of making a public method on the object you have exposed to the browser:

//this is managed code in the win forms application
public void DoSomething(args...)
//do stuff in the windows app here

The browser can now call that method by calling the document window's "external" object.

/* This is "client-side" javascript living
 in the HTML sent to the web browser */
function doSomething(){

Now you can call this javascript method as you would in normal HTML.

When it comes to argument types, there seems to be a certain amount of implicit conversion going on.  In my experiments, I found that a javascript based variable that was typed to a numeric value came into the managed method call as such.  So if you used "parseInt(...)" in javascript, you should expect that the managed call will receive a proper System.Int32 as the argument value.  In most cases what I'm dealing with are strings so the argument values slip right through without any fuss.  If the value type doesn't match up, you'll get an exception about it for sure.

The web browser control also allows you to access the browser's DOM.  The Document property returns an instance of HtmlDocument.  From here you can get at an instance of an HTML control and manipulate the DOM as you see fit from the managed code.  The browser control itself has many methods and properties to direct it as needed.

All in all, I'm finding the web browser to be a compelling tool for developing richer windows forms applications using my existing web knowledge and without the need for purchasing additional control libraries.  In a very short time, my team has been able to do some very good proof of concept work that is leading into rapid development of something that only a short time ago had me very concerned for the project timeline.  We did some initial tests with a basic prototype on various platforms to ensure we weren't getting into a snag.  The application ran happily on Windows XP, Windows 2000 and Windows Vista.  Those are our target platforms, so I'm pleased with the outcome.

The recent (yesterday) release of Google Chrome suggests that we might one day be able to integrate the open source WebKit rendering engine as an alternative to the embedded Internet Explorer browser control.  Aside from the obvious decoupling of our application from I.E., this could also mean that our application could potentially run on a non-windows .NET platform such as Mono.  However, we'll stick with I.E. and windows for now.  The enhancements we are making with the web browser rendering and .NET 2.0 upgrade are enough for this go around.

No comments: