Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Remove Microsoft.mshtml dependency

0.00/5 (No votes)
22 Oct 2008 1  
A simple approach to remove the Microsoft.mshtml dependency commonly needed with the WebBrowser control.

Introduction

I am currently working on a program called Jonathan (hosted at CodePlex) that will enable you to study a library of Spiritual Texts. After looking at many different ways of displaying texts to the user, I finally decided to use the WebBrowser control to load dynamically created HTML documents. To get everything I needed out of the WebBrowser, I was forced add a reference to Microsoft.mshtml.dll in my project. This was not that big of a deal (aside from the size), until I found out that MSHTML did not work the same on all PCs. Thus I was forced to come up with a way to remove my dependencies on Microsoft.mshtml.dll.

Background

I tried numerous ideas on how to get rid of all the references to MSHTML I had (believe me, there was a lot!), but nothing fully worked. The main way I tried was to just do without the code, or try to implement it in a different way. As I was doing this, I was looking through the methods and properties in the WebBrowser's Document object (ah, the joys of intellesense), and I stumbled upon the InvokeScript method, thank God! We can all relax now. Although I knew it was there, I never really tried to use it, because I was letting my HTML document call all the needed scripts. By the way, the only way to let your HTML document call JavaScript code on its own is to load the document via the DOM, which needs MSHTML (see below).

By using the InvokeScript method, we can call any JavaScript/VBScript method in our WebBrowser's Document. I should also point out that most of the things you need MSHTML for can be done in a scripting language like JavaScript. So it hit me, why not I convert all of my MSHTML code to JavaScript and call them via the InvokeScript method when needed. And that, boys and girls, is how I solved my problem!

Using the code

Basically, I had to find a way to change all my C# code into JavaScript, which wasn't too hard. I started off by using a StringBuilder object to create my HTML documents, and appended all the JavaScript data I needed at the beginning.

StringBuilder sb = new StringBuilder();
sb.Append("<html><head><title>Jonathan</title>");
sb.Append("<style type=\"text/css\">");
// ... Style Sheet Info ...
sb.Append("</style>");

// Here is where the real magic begins...
sb.Append("<script type=\"text/javascript\">");
// ...
sb.Append("function getTextSelection()");
sb.Append("{");
sb.Append("var tRange = document.selection.createRange();");
sb.Append("var txt = tRange.text;");
sb.Append("return txt;");
sb.Append("}");
// ...
sb.Append("</script>");
sb.Append("</head><body onload=\"resizeJonathan();\" style" + 
   "=\" background: White; color: Black; font-family: \"" +
   this.Font.Name + "\", Arial, sans-serif; font-size:" + 
   Convert.ToString(this.Font.Size + 4) + ";\">");

After that, I get all my data from a database and append the body of the HTML document.

Displaying the HTML Document

Now once the HTML document has been created in memory, we need to give it to the WebBrowser to display. Below are the old and new methods of doing this:

// Old way using Microsoft.mshtml...
pubilc void LoadHTML(string html)
{
    // If you have a lot of JavaScript in your HTML document,
    // and you do not mind using mshtml, then this is the way for you.
    // All of your JavaScript functions will be called and operate as in IE.
    mshtml.IHTMLDocument2 doc = 
      (mshtml.IHTMLDocument2)this._WebMain.Document.DomDocument;
    doc.write(html);
    doc.close();
}

// New way NO mshtml...
public void LoadHTML(string html)
{
    // This is the way to load HTML into the WebBrowser without
    // using mshtml, but you will have to call your JavaScript
    // methods in your App's code by using the InvokeScript method.
    this._WebMan.Document.OpenNew(true);
    this._WebMan.Document.Write(html);
}

Getting the Selection

Here is how you can get the WebBrowser's selection with MSHTML:

public string GetSelection()
{
    mshtml.IHTMLDocument2 doc = 
      (mshtml.IHTMLDocument2)this._WebMain.Document.DomDocument;
    mshtml.IHTMLSelectionObject sel = doc.selection;
    mshtml.IHTMLTxtRange range = (mshtml.IHTMLTxtRange)sel.createRange();
    if (range.text == null)
        return "";
    else
        return range.text.Trim();
}

And without:

// Java Script Code in your HTML Document...
function getTextSelection
{
    var tRange = document.selection.createRange();
    var txt = tRange.text;
    if(txt == null)
        return "";
    else
        return txt;
}

// Code in your app...
public string GetSelection()
{
    return this._WebMain.Document.InvokeScript(
           "getTextSelection").ToString().Trim();
}

Finding Text

This is how we originally found text in the WebBrowser with MSHTML:

public bool FindNext(string text)
{
    IHTMLDocument2 doc = (IHTMLDocument2)this._WebMain.Document.DomDocument;
    IHTMLSelectionObject sel = (IHTMLSelectionObject)doc.selection;
    IHTMLTxtRange rng = (IHTMLTxtRange)sel.createRange();
    // collapse the current selection so we start
    // from the end of the previous range
    rng.collapse(false);
    if (rng.findText(text, 1000000000, 0))
    {
        rng.select();
        return true;
    }
    else
    {
        return false;
    }
}

And without:

// JavaScript Code inside HTML Document...
var tRange = null;
function findString(strToFind)
{
    var strFound;
    if (tRange != null)
    {
        tRange.collapse(false);
        strFound = tRange.findText(strToFind);
        if (strFound) tRange.select();
    }
    if (tRange == null || strFound == 0)
    {
        strFound = tRange.findText(strToFind);
        if (strFound) tRange.select();
    }
}

// Code in you app...
public void FindNext (string txtToFind)
{
    this._WebMain.Document.InvokeScript("findString", 
        new object[] { txtToFind });
}

Points of Interest

An easy way to manage all of your added functions is to just create a new class extended from the WebBrowser class, and add all of your new code to that.

There is also a Resize method in the supplied project's code. This is used to dynamically resize the HTML document's divisions (div tags). I needed an HTML table that had static column headers. To do this, you basically create two div tags, one with a header table, and the other with a data table. The Resize function in JavaScript will size the divisions properly, but it needs to be called whenever your browser fires the Resize event.

In the supplied project, there is a method (expandSelection) that will select the word the user clicks with the right mouse button. This is a handy little feature when you want to quickly search for or define a particular word. When this function is called, it will mess up the ActiveElement in the WebBrowser's Document property. I need the ActiveElement because in my HTML document, it has several key pieces of information needed to do some routine jobs (look at my HTML code in the supplied project, the TR tags have a passage attribute that I need to determine where we are). So to get around this, I saved the HtmlElement that was in the ActiveElement property on the MouseDown event. By the time I got around to looking at the ActiveElement, it would have changed to (usually) the whole document. If this is the case, then I just go and grab the MouseDownElement I saved during the MouseDown event. This is not supplied in the given code, but if you require further assistance on it, look at the source code for Jonathan at the CodePlex website, or drop me a line.

History

  • 10/22/08 - Initial release of article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here