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\">");
sb.Append("</style>");
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:
pubilc void LoadHTML(string html)
{
mshtml.IHTMLDocument2 doc =
(mshtml.IHTMLDocument2)this._WebMain.Document.DomDocument;
doc.write(html);
doc.close();
}
public void LoadHTML(string html)
{
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:
function getTextSelection
{
var tRange = document.selection.createRange();
var txt = tRange.text;
if(txt == null)
return "";
else
return txt;
}
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();
rng.collapse(false);
if (rng.findText(text, 1000000000, 0))
{
rng.select();
return true;
}
else
{
return false;
}
}
And without:
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();
}
}
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.