Click here to Skip to main content
14,266,139 members

A Windows Forms based text editor with HTML output

Rate this:
4.92 (161 votes)
Please Sign up or sign in to vote.
4.92 (161 votes)
2 Jul 2013CPOL
A Windows Forms based text editor with HTML output, implemented with a browser control in edit mode.


NOTICE: As of 10/28/2017, a new version is available. 

Includes the following features:

  • Requires .NET 4.5.1.
  • Fixes save as and load bugs that fired browser dialogs.
  • Insert inline images either by URL or through the file system. File system images are embedded in the resulting HTML.
  • Full programmatic access to HTML, Body HTML, Body Text
  • Set the background color of the body.
  • Insert images.
  • Resize images.
  • Add and edit hyperlinks from text.
  • Add and edit hyperlinks from images.
  • Support for Windows XP SP2 to Windows 8.
  • Support for all versions of IE. 



Sample Image - editor.png



A while ago, I was working on a chat application where one of the chat clients was web based and written with ASP.NET 2.0. The other chat client was a Windows Forms based .NET application written in C#. I needed a rich text editor for my WinForms client that could output HTML, so that the ASP.NET client could display the formatting from the WinForms client. This was ruled out while using the RTF text box.

The solution I worked out was to use a WebBrowser control in edit mode within my WinForms client. Formatting buttons on a toolbar above the web browser are synchronized to the current selection in the WebBrowser control.

This article explains solutions to most of the issues involved in building an editor control from a WebBrowser control. I won't go into everything, as the source code isn't that difficult to browse. But, I do cover some of the tricks necessary to get this to work.

Using the Control

The control is an executable.

You can run it directly with it's main form, or you can embed it as a control in your own app.

To embed it into your own app, simply reference the EXE as if it were a DLL from your app in Visual Studio. 

It should show up in the Toolbox Window in Visual Studio.  

Check the source code for examples of how to access it from your code.

It's a .NET 2.0 control, so it should work in older projects. 

Setting Design Mode 

Applying design mode and establishing an editing template for the document occurs automatically when using the component. But, for reference, here is a brief explanation of how it works.

Applying design mode requires using a COM interface: adding a reference to "Microsoft HTML Object Library", which resolves to MSHTML, and adding a 'using' of 'MSHTML'.

It is necessary to add a body to the control before you can apply changes to the DOM document. To do this, you can simply apply some text to the DocumentText property of the WebBrowser control.

webBrowser1.DocumentText = "<html><body></body></html>"

Next, get a reference to the new DomDocument COM interface, and set the design mode to "On".

IHTMLDocument2 doc =
webBrowser1.Document.DomDocument as IHTMLDocument2;
doc.designMode = "On";

Finally, I replace the context menu for the web browser control so the default IE browser context menu doesn't show up.

webBrowser1.Document.ContextMenuShowing += 
new HtmlElementEventHandler(Document_ContextMenuShowing);

The browser is now in design mode, with a custom method to display the context menu.

Applying Formatting

You can apply formatting and editor functions to a browser control in design mode with the ExecCommand method on browser.Document.

Here are several examples:

public void Cut()
    webBrowser1.Document.ExecCommand("Cut", false, null);
public void Paste()
    webBrowser1.Document.ExecCommand("Paste", false, null);
public void Copy()
    webBrowser1.Document.ExecCommand("Copy", false, null);

Some commands will toggle the formatting state of the current selection.

public void Bold()
    webBrowser1.Document.ExecCommand("Bold", false, null);

public void Italic()
    webBrowser1.Document.ExecCommand("Italic", false, null);

Synchronizing Formatting Buttons with Selected Text

This is a bit more tricky than applying formatting commands to the browser control. I literally query the state of the browser editor selection every 200 ms, and set the toolbar formatting buttons based on this.

private void timer_Tick(object sender, EventArgs e)
    boldButton.Checked = IsBold();
    italicButton.Checked = IsItalic();
    underlineButton.Checked = IsUnderline();
    orderedListButton.Checked = IsOrderedList();
    unorderedListButton.Checked = IsUnorderedList();
    linkButton.Enabled = SelectionType == SelectionType.Text;

    if (Tick != null) Tick();

You'll notice that there is a Tick event that is fired at the end of the timer's tick event. External components can subscribe to this event to update the state of their GUI. For example, they may update the Enabled state of cut/copy/paste/undo/redo buttons based on the state of the Editor control.

I do this by using the COM document interface retrieved from the WebBrowser control with:

IHTMLDocument2 doc = webBrowser1.Document.DomDocument as IHTMLDocument2;

Then I use queryCommandState to determine the state of the current selection:

public bool IsBold()
    return doc.queryCommandState("Bold");

public bool IsItalic()
    return doc.queryCommandState("Italic");

public bool IsUnderline()
    return doc.queryCommandState("Underline");

The link button and font controls are managed in a similar way, but I'll leave that for you to check out in the code.


Focusing the control isn't necessarily simple either. The WebBrowser control itself will not accept focus. Neither will the WebBrowser control's document. But, the body will focus, assuming there is a body element on the control.

private void SuperFocus()
    if (webBrowser1.Document != null && 
        webBrowser1.Document.Body != null)

Of course, you never need to call this method directly. Calling the Focus method on the control will place the focus on the editor control containing the WebBrowser control. The editor control will transfer focus to the WebBrowser's document body automatically, when it receives the GotFocus event.

Retrieving Text or HTML

The BodyHtml and BodyText methods retrieve the body HTML and text, respectively.

Linking to the Component Assembly

In Visual Studio 2005, you can link to an assembly (add a reference), even if the assembly is an executable. The editor is written as a component embedded in a form, so you can add this component to the control palette and drag and drop into your application. The control's name is Editor, under namespace Design.


The .NET 2.0 WebBrowser control can effectively be used as a text editor. This is useful when you need an editor control that can be used as an HTML editor. The implementation is not completely straightforward in some areas. This article attempts to show some of the tricks necessary to get it to work.


  • 10/04/2006 - various bug fixes and design updates / designer support.
  • 10/19/2006 - more bug fixes and more designer support.
  • 10/28/2017 - Upgrade to .net 4.5.1 and VS 2015


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

kevin delafield
United States United States
No Biography provided

Comments and Discussions

GeneralRe: Problem because Printing is asynchronous Pin
DanCooperstock26-Jun-07 6:52
memberDanCooperstock26-Jun-07 6:52 
GeneralPerformance Pin
rensdenobel200013-May-07 22:22
memberrensdenobel200013-May-07 22:22 
GeneralHTML Source index Pin
Manchovski7-May-07 10:12
memberManchovski7-May-07 10:12 
QuestionHowTo suppress "document changed" MsgBox? Pin
lightwaver30-Apr-07 9:17
memberlightwaver30-Apr-07 9:17 
AnswerRe: HowTo suppress "document changed" MsgBox? Pin
lightwaver30-Apr-07 9:43
memberlightwaver30-Apr-07 9:43 
AnswerRe: HowTo suppress "document changed" MsgBox? Pin
DerekAndDomino7-Aug-07 7:27
memberDerekAndDomino7-Aug-07 7:27 
QuestionSome clues, but no solution... Pin
John Hind11-Sep-07 0:31
memberJohn Hind11-Sep-07 0:31 
AnswerRe: Some clues, but no solution... Pin
reo636-Nov-07 8:56
memberreo636-Nov-07 8:56 
I have a long response that might provide a few clues to someone who wants to try this path.

First off, it seems that the SetDirty command only has a true setting.
You mark a document dirty.
Marking a document dirty to false did not seem like it did anything to me.
(Feel free to challenge me on that if you like.)

Second, MS offers a clue at: ""

The code almost works if you follow their instructions...

<br />
//<br />
using mshtml;<br />
using SHDocVw;<br />
<br />
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]<br />
public struct OLECMDTEXT<br />
{<br />
    public uint cmdtextf;<br />
    public uint cwActual;<br />
    public uint cwBuf;<br />
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]<br />
    public char rgwz;<br />
}<br />
<br />
[StructLayout(LayoutKind.Sequential)]<br />
public struct OLECMD<br />
{<br />
    public uint cmdID;<br />
    public uint cmdf;<br />
}<br />
<br />
// Interop definition for IOleCommandTarget. <br />
[ComImport,<br />
Guid("b722bccb-4e68-101b-a2bc-00aa00404770"),<br />
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]<br />
public interface IOleCommandTarget<br />
{<br />
    //IMPORTANT: The order of the methods is critical here. You<br />
    //perform early binding in most cases, so the order of the methods<br />
    //here MUST match the order of their vtable layout (which is determined<br />
    //by their layout in IDL). The interop calls key off the vtable ordering,<br />
    //not the symbolic names. Therefore, if you switched these method declarations<br />
    //and tried to call the Exec method on an IOleCommandTarget interface from your<br />
    //application, it would translate into a call to the QueryStatus method instead.<br />
    void QueryStatus(ref Guid pguidCmdGroup, UInt32 cCmds,<br />
        [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] OLECMD[] prgCmds, ref OLECMDTEXT CmdText);<br />
    void Exec(ref Guid pguidCmdGroup, uint nCmdId, uint nCmdExecOpt, ref object pvaIn, ref object pvaOut);<br />
}<br />
<br />
// boring code removed<br />
<br />
        //Guid cmdGuid = new Guid("ED016940-BD5B-11CF-BA4E-00C04FD70816");<br />
        //Guid cmdGuid = new Guid("d4db6850-5385-11d0-89e9-00a0c90a90ac");<br />
        Guid cmdGuid = new Guid("DE4BA900-59CA-11CF-9592-444553540000");<br />
        IOleCommandTarget m_oleTarget;<br />
<br />
// more boring code removed<br />
<br />
            string sError = string.Empty;<br />
            try<br />
            {<br />
                int iStatus = -1;<br />
                m_oleTarget = (IOleCommandTarget) m_docHtml; <br />
                object o1 = (object)null;<br />
                object o2 = (object)null;<br />
                //<br />
                Type t1 = webEdit.GetType();<br />
                Type t2 = m_docHtml.GetType();<br />
                Type t3 = m_oleTarget.GetType();<br />
                //<br />
                m_oleTarget.Exec(<br />
                    ref cmdGuid, // GUID<br />
                    (uint)90,  // IDM_FONT<br />
                    (uint)SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_PROMPTUSER, <br />
                    ref o1,   // pvaIn<br />
                    ref o2);  // pvaOut<br />
                iStatus++;<br />
            }<br />
            catch (Exception exc)<br />
            {<br />
                sError = exc.Message;<br />
            }<br />
<br />

If you call m_oleTarget.Exec() with the wrong GUID in cmdGuid, it throws.
If you get the right Guid, it will work.

I had working code from a working COM C++ application that did one of these.
(Cut & Paste engineering at it's finest from April 2003 for a company I used to work for.)
And I was able to use the Guid it showed in debug mode in the C# code.
(Kind of like using the answer in the back of the math book to help arrive at how to solve
the problem.)

The funny thing is the GUID's in t1, t2, t3 do not match the working Guid.
That working Guid is not even in the Registry (search does not find it).

Big question:
Why is the good Guid working at all?
Can the working Guid be arrived at programmatically for if/when it changes in the future?
Does the working Guid work for lots of people / machines? (So far it works on my XP box
and a Win 2000 PC)
GeneralRe: HowTo suppress "document changed" MsgBox? Pin
Massimo Colurcio26-Nov-07 15:05
memberMassimo Colurcio26-Nov-07 15:05 
GeneralRe: HowTo suppress "document changed" MsgBox? Pin
jammmie99913-Feb-09 8:00
professionaljammmie99913-Feb-09 8:00 
GeneralRe: HowTo suppress "document changed" MsgBox? Pin
NC3D18-Nov-12 10:18
memberNC3D18-Nov-12 10:18 
QuestionUnicode problem Pin
Paul Shaffer14-Mar-07 10:12
memberPaul Shaffer14-Mar-07 10:12 
AnswerRe: Unicode problem Pin
Kastellanos Nikos10-Apr-07 22:34
memberKastellanos Nikos10-Apr-07 22:34 
GeneralRe: Unicode problem Pin
getcode10111-Feb-09 13:25
membergetcode10111-Feb-09 13:25 
GeneralGreat Article!!!!!! Pin
Shailendra Sason9-Mar-07 19:07
memberShailendra Sason9-Mar-07 19:07 
GeneralFollow Link Pin
baer9999-Mar-07 4:55
memberbaer9999-Mar-07 4:55 
QuestionRe: Follow Link Pin
Phil Jeffrey25-Mar-07 16:55
memberPhil Jeffrey25-Mar-07 16:55 
AnswerRe: Follow Link Pin
John Hind20-Aug-07 4:54
memberJohn Hind20-Aug-07 4:54 
GeneralWeird problem Pin
denuz28-Feb-07 7:26
memberdenuz28-Feb-07 7:26 
GeneralRe: Weird problem Pin
Bardo7572-Mar-07 1:07
memberBardo7572-Mar-07 1:07 
GeneralRe: Weird problem Pin
laue26-Apr-07 9:43
memberlaue26-Apr-07 9:43 
QuestionUse as form builder. Pin
kharkov9219-Feb-07 6:22
memberkharkov9219-Feb-07 6:22 
QuestionHelp- How can I copy/paste image from Image Editor? Pin
fuqiangx15-Feb-07 9:37
memberfuqiangx15-Feb-07 9:37 
AnswerRe: Help- How can I copy/paste image from Image Editor? Pin
maggio28-May-07 21:43
membermaggio28-May-07 21:43 
QuestionCan anyone do this? Kevin? Pin
El Gordo 776-Feb-07 8:46
memberEl Gordo 776-Feb-07 8:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Posted 12 Sep 2006


403 bookmarked