MSHTML Hosting – Editing

Did you know that you can turn WebBrowser into a decent WYSIWYG HTML editor? It’s true and it’s relatively simple. There are actually two ways to turn on the editor mode: contentEditable markup attribute, and IHTMLDocument2::designMode method. Since we are talking about embedding WebBrowser in an application, the IHTMLDocument2::designMode method is the most typical choice.

The basic steps are:

  1. Navigate to “about:blank”
  2. Wait for the blank page to load
  3. set designMode to “on”

At this point you should be able to type, select, cut, copy and paste into the WebBrowser. But more than that, the editor allows you to quickly add various kinds of HTML formatting markup, such as: Font (Name, Size and Style), Colors, Horizontal lines, Paragraph and line breaks, Justification, and Indenting. It does this through a nice little interface called IOleCommandTarget. The interface has only two methods: QueryStatus and Exec. The interface can be used with many different OLE servers, such as Microsoft Word and Excel, but it is especially useful with WebBrowser.

Using IOleCommandTarget

At the heart of IOleCommandTarget is command identifiers. All commands supported by an implementer of IOleCommandTarget must have an identifier. Here is a list of MSHTML commands. Getting an instance of IOleCommandTarget is pretty simple: QueryInterface IHTMLDocument2 for it.


IHTMLDocument2* pDoc = ...;

// Turn on editor mode
pDoc->put_designMode(CComBSTR(L"on"));

// Execute some commands
IOleCommandTarget* pCmdTarget = 0;
hr = pDoc->QueryInterface(IID_IOleCommandTarget, (void**)&pCmdTarget);
if (SUCCEEDED(hr)) {
  // do commands here
  // ...
  pCmdTarget->Release();
}

QueryStatus provides a way to get the current state of a command. It is of limited value for most commands. Most uses include retreiving the current value of formatting commands. So, if you want to know if a command is supported, QueryStatus does the job. In addition, QueryStatus can be used to determine whether some toggle-type commands, such as IDM_BOLD, are “on” or “off”. The following code checks to see if various Edit menu items should be enabled based on the current selection or clipboard contents:


OLECMD Cmnds[5];
::ZeroMemory(Cmnds, sizeof(OLECMD)*5);
Cmnds[0].cmdID = IDM_COPY;
Cmnds[1].cmdID = IDM_PASTE;
Cmnds[2].cmdID = IDM_CUT;
Cmnds[3].cmdID = IDM_DELETE;
Cmnds[4].cmdID = IDM_SELECTALL;

if (SUCCEEDED(pCmdTarget->QueryStatus(&CGID_MSHTML, 5, Cmnds, NULL)))
{
  bool bCanCopy = Cmnds[0].cmdf & OLECMDF_ENABLED;
  bool bCanPaste = Cmnds[1].cmdf & OLECMDF_ENABLED;
  bool bCanCut = Cmnds[2].cmdf & OLECMDF_ENABLED;
  bool bCanDelete = Cmnds[3].cmdf & OLECMDF_ENABLED;
  bool bCanSelectAll = Cmnds[4].cmdf & OLECMDF_ENABLED;
}

Exec is pretty simple. It provides the way to change the current value, insert a new item or perform an action. Exec allows commands to pass extra information or retrieve information via two VARIANT parameters. Some examples:


// Set current selection to bold
pCmdTarget->Exec(&CGID_MSHTML, IDM_BOLD,
                 OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);

// Set current selection to tahoma
pCmdTarget->Exec(&CGID_MSHTML, IDM_FONTNAME,
                 OLECMDEXECOPT_DONTPROMPTUSER, CComVariant("Tahoma"), NULL);

// Set current selection to font size 2 (HTML font sizes, not points)
pCmdTarget->Exec(&CGID_MSHTML, IDM_FONTSIZE,
                 OLECMDEXECOPT_DONTPROMPTUSER, CComVariant(2), NULL);

// Copy current selection to clipboard
pCmdTarget->Exec(&CGID_MSHTML, IDM_COPY,
                 OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);