Curiosity is bliss    Archive    Feed    About    Search

Julien Couvreur's programming blog and more

Hosting a web browser component in a C# winform

 

Many winform applications need to render some html: blog aggregators, news/mail readers or any kind of application that wants to use html as their output facility (reporting tools, log analyzers,...).
We'll go through the details of using the "Microsoft Web Browser" COM component in a C# winform, with and without VS.net. A couple of the most useful APIs will be explained and provided samples for.

Using VS.net
Let's first get the browser component into the form.
In the "Customize Toolbox..." context menu (on the Toolbox), pick "Microsoft Web Browser" from the COM components list. This will add an "Explorer" control in the "General" section of the Toolbox.
You can drag and drop this control onto your form and voila !

With this control (named axWebBrowser1 by the winform designer), you will be able to browse the web, but if you want to render your own (locally generated) HTML, you will also need some interfaces like IHTMLDocument2 and IHTMLElement.
To make these available, go to the "Add a Reference" wizard in the Solution Explorer and pick "Microsoft.mshtml" in the ".NET" tab.
Adding the "using mshtml;" declaration to your code or using full class names (mshtml.IHTMLDocument2) will give you access to all the needed classes.

Using the command line only
We now want to make these classes available, but to a command-line project (no VS.net help).

First, let's generate the interop dlls for the ActiveX browser component: SHDocVw.dll and AxSHDocVw.dll. This is what VS.net does (sliently) when you added this component to your toolbox.
Run "aximp c:\WINNT\system32\shdocvw.dll".
At this point you can compile a form that uses the AxSHDocVw.AxWebBrowser class, using "csc /r:SHDocVw.dll,AxSHDocVw.dll YourForm.cs".
In the code, the "using AxSHDocVw;" statement will the classes available to you, or you can use the full class names (AxSHDocVw.*).

Now, to also make the IHTMLDocument2 and IHTMLElement interfaces available to us, we need to generate an interop dll for the mshtml COM object.
To do so, run "tlbimp c:\WINNT\system32\mshtml.tlb".
Compiling the form now becomes "csc /r:SHDocVw.dll,AxSHDocVw.dll,MSHTML.dll YourForm.cs"

Note: it seems that the .NET framework already provides an interop dll for the mshtml COM object. It seems to be called a "primary interop dll", but I am not sure yet what it is or how to reference it. I'll add details here as I find them.

Programming the AxSHDocVw.AxWebBrowser control
First let's load a web page. For this you just need to call the Navigate method like shown below:

object o = null;
axWebBrowser1.Navigate("http://google.com", ref o, ref o, ref o, ref o);

You'll notice that the Navigate method is asynchronous, ie. the call returns before the web page is completely loaded and displayed. You can get a notification of completion by registering to the NavigateComplete2 event (with a AxSHDocVw.DWebBrowserEvents2_NavigateComplete2EventHandler handler).
In the InitializeComponent() method generated by VS.net, or wherever you do your events hook-up, add:

this.axWebBrowser1.NavigateComplete2 += new DWebBrowserEvents2_NavigateComplete2EventHandler(this.navigateComplete2);

Where your callback looks like:

private void navigateComplete2(object sender, DWebBrowserEvents2_NavigateComplete2Event e)
{
// Page loaded
}

This notification is useful because you can't feed your own html into the component until a page is loaded. To expedite this loading you can use the "about:blank" url instead of google's.
Then, in the event handler (or in any code that gets executed only after the component has the blank page loaded) you can feed it your html with:

private void navigateComplete2(object sender, DWebBrowserEvents2_NavigateComplete2Event e)
{
IHTMLDocument2 htmlDocument = (IHTMLDocument2)axWebBrowser1.Document;
IHTMLElement body = (IHTMLElement)htmlDocument.body;

body.innerHTML = "<html><body>hello world</body></html>";
}

You now have the ability to browse the web or display your own html, within your winform!

Memory considerations
With my first attempt of embedding a browser in a winform, I got a 27Megs runtime memory usage for the winform. That seemed high so I looked at it some more.
I got it down to 20Megs by removing all the un-necessary "using" statements that VS.net had generated for me.
The overall break-down for the memory usage is as follows:
- an empty winform with "using" cleanup: 11Megs,
- a winform with cleanup and unused browser (no Navigate call): 15Megs,
- a winform with cleanup and used browser: 19Megs.
The numbers provided are approximate. Also, as with any GC system, the memory usage always seems higher than it really needs to be until the GC performs its collection, so it is difficult to know exactly what the memory usage is...

MissingManifestResourceException
When I tried to compile and run the code from my VS.net project in the command-line, I got an exception:

An unhandled exception of type 'System.Resources.MissingManifestResourceException' occurred in mscorlib.dll

Additional information: Could not find any resources appropriate for the specified culture (or the neutral culture) in the given assembly. Make sure "Form1.resources" was correctly embedded or linked into assembly "Form1".
baseName: Form1 locationInfo: BrowserTest.Form1 resource file name: Form1.resources assembly: Form1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

I haven't found the exact reason for this exception, but I got ride of it by removing all references to "resources" from the code.

Links
MSDN reference on aximp utility
MSDN reference on tlbimp utility
An article on hosting the ASP.NET runtime in an application.

Update: I just found out a codeproject article that uses some more APIs of the WebBrowser component in C#. Unfortunately, after building and running the browser I wasn't able to actually use or see these features, except the "Edit" (pen icon).

Update (Oct 10, 2003):
Binding the DOM and the winform
One question I still had was about extending the scripting APIs available within the component. For example, if you display an html page in the browser component, how can javascript inside that page interact with the application/winform itself? It is possible, as described by this page (MSDN) and in this C# example (The Code Project). Here is another C# example (The Code Project).

For example, if we include a button inside our html, we can bind a C# listener method to the click event for that html element, as the following code shows:

private void navigateComplete2(object sender, DWebBrowserEvents2_NavigateComplete2Event e)

{

 IHTMLDocument2 htmlDocument = (IHTMLDocument2)axWebBrowser1.Document;
 IHTMLElement body = (IHTMLElement)htmlDocument.body;

 body.innerHTML = "<html><body>hello world <button id=buttonID>Click Me</button> </body></html>";

 HTMLButtonElement button = (HTMLButtonElement) htmlDocument.all.item("buttonID", null);

 ((HTMLButtonElementEvents2_Event) button).onclick += new HTMLButtonElementEvents2_onclickEventHandler(this.button_click);

}

private bool button_click(IHTMLEventObj e)
{

 MessageBox.Show("button clicked");
 return true;

}

This example shows how the C# world can manipulate the DOM. Doing the reverse, by giving the DOM world some access to C# methods and variables would be interesting too. I'm experimenting with it right now, following the article on the Code Project, but I am getting a 'null' error in the browser. The author mentions getting the same error (and spending two weeks to solve it), but didn't post the details of his solution...

Links:
Browser automation in C#.

______________________________________

I haven't had time to play with this (it requires a full Mozilla development environment), but it seems promising: a Gecko wrapper in managed C++.

You can find it at:
http://lxr.mozilla.org/seamonkey/source/embedding/wrappers/DotNETEmbed/ManagedGecko.html

Posted by: Dumky (June 20, 2003 12:19 PM) ______________________________________

As a complement you can even host an ASP.Net runtime in your app, check this very detailled article out: http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.asp

Posted by: Dumky (June 29, 2003 12:00 AM) ______________________________________

re: MissingManifestResourceException

Got this today and just couldn't believe it. Didn't even have a resource file (that i knew of). Thanks to Michael Covington (uga) for posting the underlying problem...

I had moved an enum def outside the Form class to the namespace. If you have a class def before the Form class the compiler gets confused (wrong resource file name?) and generates this exception.

details at http://www.ai.uga.edu/~mc/VisualStudioGotchas.html

Posted by: Dhominator (September 3, 2003 02:42 PM) ______________________________________

re:

ha! lame explanation by MS... functioning as designed. Jah, right :p

http://support.microsoft.com/?kbid=318603

Posted by: Dhominator (September 3, 2003 02:45 PM) ______________________________________

same thing can i do with out using webbrowser control.is there anyway.please let me know the solution if there is

Posted by: praveen (November 24, 2004 02:17 AM) ______________________________________

I want to do the same thing in asp.net but I don't know the way to access the property of web browser component. Plz Help

Amit Shrivastava

Posted by: Amit Shrivastava (June 2, 2005 11:06 PM) ______________________________________

Other events are dead. It's imposible to write or click in html page....

Posted by: NeK (August 16, 2005 01:26 AM) ______________________________________

With your example I was not able to assign "Hello world" string in NavigateComplete2 event handler because it seems that body object is not initialized yet.

Posted by: manovich (October 1, 2005 04:16 AM) ______________________________________

If you do at least one call to axWebBrowser1.Navigate() first (even with the url "about:blank") then the body will be initialized properly.

Posted by: Jeff (October 26, 2005 07:37 AM) ______________________________________

How can I save the file to local disk as HTML that was loaded by Web Browser control? Thanks for the help.

Posted by: Vijay (November 7, 2005 01:10 PM) ______________________________________

don't call .Navigate() in the form's Onload event - this was causing the control to lock up on my system

Posted by: lammy (December 7, 2005 02:29 AM) ______________________________________

Just what I was looking for... Thanks for the awesome explaination.

Posted by: a (March 8, 2006 09:28 PM)
comments powered by Disqus