Jaa


Using the WebBrowser Control as a Powerful DHTML Editor in Windows Forms

As you may know, I love the WebBrowser control. You can do so many wonderful things with it. And one of those things is whipping up a quick, simple, but surprisingly rich DHTML editor.

This interface was easy to concoct with the ToolStrip control and images taken from the FreeTextBox page. The WebBrowser control is positioned right below the ToolStrip.

To make the page editable, you need merely feed the WebBrowser a document whose BODY has the contentEditable property set to true.

webBrowser1.DocumentText = "<HTML><BODY contentEditable='true'></BODY></HTML>";

Now, the tricky part was programming the ToolStrip control to update the buttons depending upon the current style settings inside of the document. What's cool about tripping contentEditable on the document is that shortcut keys are now available. I.e., your users can press CTRL+B to bold, CTRL+I to italicize, etc. - all without your writing a lick of code. But, being a good software designer, you want your toolbar buttons to be sticky. You want them to look pressed when the cursor is placed in the middle of some bold text, and unpressed when the text is not bold.

The problem is that the Windows Forms team didn't implement all of the wrapper calls to make this easy for us. Ideally, you would sync the OnCommandStateChange event to get a notification when a style has changed. You would then use queryCommandState() on the document object to determine whether or not a given style is set or unset, and update the toolbar appropriately. But neither was implemented! OnCommandStateChange was broken up into events for the forward and back buttons, but the command state change element of the event was left out entirely. And queryCommandState() is not implemented at all on HtmlDocument.

So to make this work, I first had to add the MSHTML library so I could make an interop call. (In VS, right-click your project, select Add Reference..., and choose MSHTML Library from the COM tab. VS will generate an RCW, or Runtime Callable Wrapper, for you.)

Next, I named the buttons after the commands you have to pass to HtmlDocument.ExecCommand() in order to enable and disable the style within DHTML.

Finally, I implemented a method that is called from a Timer event every 10 milliseconds. This method checks each of the styles in turn, and updates the pressed state of the ToolStrip buttons as appropriate. (Note: This code doesn't show implementation for the font controls.)

        private void CheckCommandStateChanges()
{
mshtml.IHTMLDocument2 doc = (mshtml.IHTMLDocument2)webBrowser1.Document.DomDocument;

            // Test bold.
string[] commands = { "Bold", "Italic",
"Underline", "InsertOrderedList",
"InsertUnorderedList"};

foreach (string command in commands)
{
ToolStripButton button =
(ToolStripButton)editStrip.Items[command + "Button"];

                if (doc.queryCommandState(command))
{
// Get button.
if (button.CheckState != CheckState.Checked)
{
button.Checked = true;
}
}
else
{
if (button.CheckState == CheckState.Checked)
{
button.Checked = false;
}
}
}
}

Now, yes, I could have been a little less hacky about this by importing the WebBrowser control as well and just listening for onCommandStateChange. I didn't for two reasons:

  1. I dislike Interop, and avoid it wherever possible. It's hard to debug if anything goes wrong, and event callbacks from COM to .NET make me nervous. I dealt with this stuff heavily when writing my article on .NET UserControls in Internet Explorer, and have sworn off that brand of hooch ever since.
  2. I didn't want my app to have the overhead of importing two heavy COM libraries. Having to drag in MSHTML when we have a managed wrapper for the core classes was bad enough.

For my next trick, I set up the code so that pressing the style buttons on the ToolStrip would change the style in the document. First, I set each button to point to the same event handler.

            BoldButton.Click += new EventHandler(commandButton_Click);
            ItalicButton.Click += new EventHandler(commandButton_Click);
            UnderlineButton.Click += new EventHandler(commandButton_Click);
            InsertUnorderedListButton.Click += new EventHandler(commandButton_Click);
            InsertOrderedListButton.Click += new EventHandler(commandButton_Click);

I set the Tag property for each button to the name of the associated command used by HtmlDocument.ExecCommand(). Then, I wrote the event handler that handles all of these events. (Note that I have to turn my ToolStrip updating timer OFF.)

        private void commandButton_Click(object sender, EventArgs e)
{
timer1.Enabled = false;
ToolStripButton button = (ToolStripButton)sender;

            // We don't need to check the state -
// ExecCommand acts as a toggle, turning the
//command on if it's
// off, and off if it's on. Just ensure that
            //the Tag property of your button is set to the
// appropriate command.
webBrowser1.Document.ExecCommand(button.Tag.ToString(), false, null);
timer1.Enabled = true;
}

Doing it this way is nice, because you can now add any number of additional buttons corresponding to other styles and tags, and your code will be able to handle them.

And them's the basics. A simple DHTML Editor in your Windows Forms applications with no more than 90 lines of code. If you need to provide your users rich editing and export the results in HTML format, it's hard to go wrong with this solution.

Comments