MSHTML Hosting – More Tricks

Here are a couple miscellaneous tips for using the WebBrowser while in design (edit) mode.

Setting Focus In Design Mode

With the WebBrowser control in design mode, there are times that focus (and the blinking cursor) are not set correctly. For example, with focus in the editor, ALT+TAB away from your application, and then back again. Focus may not be set back into the editor. Here is a simple way to fix it. Put this code in an event or message handler that gets called when your application is re-activated:


IHTMLDocument2* pHTMLDoc2 = ...;
IHTMLWindow2* pWindow = 0;
pHtmlDoc2->get_parentWindow(&spWindow);
if (pWindow) {
  pWindow->focus();
  pWindow->Release();
}

Unselecting Current Selection

IHTMLTxtRange has a method to easily select a range of text, but there is no simple method to unselect an existing text selection. Here is a simple way to do it:


IHTMLDocument2* pHTMLDoc2 = ...;
IHTMLSelectionObject* pSelection = 0;
pHTMLDoc2->get_selection(&pSelection);
if (pSelection) {
  IDispatch* pDispRange = 0;
  pSelection->createRange(&pDispRange);

  IHTMLTxtRange* pTxtRange = 0;
  pDispRange->QueryInterface(IID_IHTMLTxtRange, (void**)&pTxtRange);
  if (pTxtRange) {
    VARIANT_BOOL bSuccess;
    pTxtRange->execCommand(CComBSTR(L"Unselect"), VARIANT_FALSE, CComVariant(), &bSuccess);
    pTxtRange->Release();
  }
  pDispRange->Release();
  pSelection->Release();
}

SVG & DHTML Applications

I have been tinkering a lot lately with the concept of writing DHTML-based desktop applications running in a custom web browser. The custom browser would be made to look like the host shell of a standard Windows application with normal menus and toolbars. One of the pieces missing from DHTML applications in the past has been the lack of a graphics rendering system. Scalable Vector Graphics (SVG) fills this hole nicely. In fact, SVG will create graphics that rival any graphics library, desktop or otherwise. If you have not played with SVG, you owe it to yourself to have a look.

Besides static graphic rendering, SVG also builds on all the dynamic concepts in DHTML. SVG elements can have attached event handlers for mouse and keyboard events, among others. Javascript can be used to handle any of the events. SVG elements can also be animated very easily.

Currently, SVG is supported in MSHTML/WebBrowser via ActiveX viewers from Adobe and Corel. Mozilla/Firefox browsers will have native support sometime in 2005.

I think it is also worth mentioning the MSHTML/WebBrowser does support a native graphic markup language called Vector Markup Language (VML), which predates SVG and is very similar in design. If you want to natively support graphics (No ActiveX), you should consider VML.

MSHTML Hosting – Editing Tricks

Show Table Borders

This is a little feature like you find in MS Word. It will display table invisible borders in light gray color so you can see the structure of the table even if borders are turned off. The feature only works when the WebBrowser is in design mode. Since it’s an IOleCommandTarget editing command ID, it’s quite simple to use:


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)) {
  pCmdTarget->Exec(&CGID_MSHTML, IDM_SHOWZEROBORDERATDESIGNTIME,
                   OLECMDEXECOPT_DONTPROMPTUSER, CComVariant(true), NULL);
  pCmdTarget->Release();
}

Note the MSDN lists editing ID’s separately from regular ID’s.

Use DIV for Paragraph Breaks

Normally, when you press ENTER in design mode, WebBrowser will insert a paragraph tag, <P>, as a break. This can create large amounts whitespace between paragraphs since <P> tags are rendered with more padding than a simple line break. You can setup a CSS class or a style change the padding or you could tell WebBrowser to use <DIV> tags instead. There are two ways I know of to get WebBrowser to use <DIV> tags:

MSHTML Hosting – More Editing

Previously I talked about exposing the HTML editor hiding inside the WebBrowser control. I also talked about IOleCommandTarget and how you can control formatting and layout features of the editor. It won’t take long before you discover that the IOleCommandTarget command ID’s are somewhat limited. At some point, you will need to insert specialized text or HTML at the current cursor location or replacing the current selection. Luckily for us, there is an easy way to do it: IHTMLTxtRange.

I talked about IHTMLTxtRange in a previous post. It is useful for finding text or getting the text of the current selection. It can also do the reverse: It can insert text or HTML at the current cursor location or selection using the pasteHTML method. Here’s a simple example:


IHTMLDocument2* pDoc = ...;

IHTMLSelectionObject* pSelection = 0;
HRESULT hr = pDoc->get_selection(&pSelection);
if (SUCCEEDED(hr)) {
   IDispatch* pDispRange = 0;
   hr = pSelection->createRange(&pDispRange);
   if (SUCCEEDED(hr)) {
      IHTMLTxtRange* pTextRange = 0;
      hr = pDispRange->QueryInterface(IID_IHTMLTxtRange, (void**)&pTextRange);
      if (SUCCEEDED(hr)) {
         CComBSTR sText = L"This is <b>better</b> text";
         pTextRange->get_pasteHTML(sText);
         pTextRange->Release();
      }
      pDispRange->Release();
   }
   pSelection->Release();
}

pDoc->Release();

In the above example, the selection may have selected text or it may just be a cursor location. The HTML overwrites any selected text. Otherwise, it’s inserted at the cursor location.

Overall, IHTMLTxtRange is a very useful interface. I have used it for implementing spell check, find/replace and inserting complex HTML. Just to be clear, IHTMLTxtRange works when design mode is off or on. The WebBrowser control does not need to be in edit mode to call pasteHTML.

Hello JavaScript

Coming from a C++ background, I found JavaScript very comfortable when I started doing DHTML programming. I was impressed with some of the things it could do with properties and functions. Other dynamic languages, Python and Ruby for example, have been getting lots of press lately while JavaScript has been quietly powering the Web.

That seems to be changing. I am reading a lot more about JavaScript. This is due in large part to Google’s GMail and Suggest (how it works). These are great examples of what can be done with DHTML and created a lot of buzz. The JavaScript Weblog posted a summary of predictions that seem to be shared by others.

Go JavaScript!

MSHTML Hosting – User Content

I have been posting about using MSHTML to display HTML-based content in your Windows applications. Some of the reasons I started doing this in my own code include:

  • Easy way to create a modern looking task-based UI.
  • Flexible and extensible, with a built-in script engine (JavaScript) and style system (CSS).
  • Based on open standards

There is another interesting by-product:

  • Very easy to allow end users to add their own custom HTML.

You’re running a full-blown web browser inside your application. All you need to do is provide some way for the user to specify their own URL. It could be from within the application UI or maybe a special registry key. You pass the URL to Navigate and their custom content appears in your application. What a great way to open your application to your end users.

MSHTML Hosting – Calling JavaScript From Host

I don’t know how often someone would want to call a JavaScript (JS) function in the WebBrowser control from the host application. Since the host can control many, if not more, aspects of the HTML content externally using IDocHostUIHandler or the MSHTML DOM interfaces, it seems unnecessary to implement a method in JS and call that method from the host. But, if you need to for some reason, here’s how to do it:


IHTMLDocument* pHTMLDoc = /* however you can get the IHTMLDocument */

DISPID idMethod = 0;
OLECHAR FAR* sMethod = L"DoSomething";
IDispatch* pScript = 0;
pHTMLDoc->get_Script(&pScript);
HRESULT hr = pScript->GetIDsOfNames(IID_NULL, &sMethod, 1, LOCALE_SYSTEM_DEFAULT,
                                    &idSave);
if (SUCCEEDED(hr)) {
  // invoke assuming no method parameters
  DISPPARAMS dpNoArgs = {NULL, NULL, 0, 0};
  hr = pScript->Invoke(idSave, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD,
                       &dpNoArgs, NULL, NULL, NULL);
}
pScript->Release();
pHTMLDoc->Release();

Notice we are using the IHTMLDocument interface, not the more often used IHTMLDocument2 interface. Also note that I am assuming no arguments are passed to the JS method. Check out IDispatch::Invoke for more information on calling a method with arguments. CodeProject has a few nice articles as well.

DHTML-Based Desktop Applications

Recently, I’ve had several discussions with other developers about UI frameworks. One of the more interesting outcomes is my new desire to create desktop applications using traditionally web-based frameworks. I know it sounds weird, but after looking at the current UI framework landscape and factoring in the latest directions in rich Internet applications, it may not be too crazy. People like Jon Udell and Adam Bosworth have spent considerable time discussing the subject. Currently, there are several possible UI frameworks to choose from for commercial, shrink-wrapped applications (it’s what I do):

  • MFC (Win32) – This popular framework should be avoided for new development. It’s a dinosaur and will drag your application back into the stone age. It’s quickly losing its leading edge status.
  • WTL (Win32) – This framework makes good use of modern C++ techniques, but it’s in somewhat of a niche. Again, I don’t think it should be used for new development.
  • VCL (Win32/NET) – I have always liked using this framework. It’s really a predecessor to WinForms and .NET libraries. Ease of use and UI productivity is definitely high. There’s even a nice migration to .NET from Win32. Borland’s commitment is the biggest problem with this framework.
  • WinForms (NET) – The newest framework on block. Same RAD benefits of VCL, without the Borland problem. Well, not exactly. Microsoft is likely to moth-ball this framework before it really has a chance to succeed.
  • Avalon (NET) – The “on-deck” framework, not exactly ready for prime time. You can bet Microsoft will throw a lot of developer support at the framework, but when will it be production ready? And why only run on WinXP and greater? Win2K will still be active for a while and in numbers that commercial applications won’t be able to ignore. Perhaps the security benefits of WinXP will cause migration to occur faster.
  • Rich Internet Apps (DHTML / FLEX / Laszlo / XUL) – Not really a framework, but a new category altogether. These kinds of applications are generally browser-based and are really starting to generate a lot of interest. Providing rich applications through a browser results in a better user experience than traditional webpage applications. In addition, these applications are built on frameworks that are also enterprise-ready, with regard to deployment and scalability. However, there appears to be no traction in the desktop application area.

Overall, the UI framework landscape is in the worst shape I have seen in years. As bad as I feel about MFC, at least there was a time where it was ubiquitous, leading-edge and strongly supported. Those days are gone and no other framework has stepped up yet (in reality, not hype). WinForms is probably the safest way to go for now for the following reasons:

  • It exists right now. Version 2 will be delivered in 2005.
  • It is currently built on Win32 API and support for Win32 API will not go away quickly.

Something else I think about is risk. The UI frameworks listed above all have some form of vendor lock-in or platform lock-in. I don’t feel DHTML has lock-in. I am free to use various implementations on various platforms. It seems like less of a risk when I am not dependent on another company’s UI framework. Hey, I try to separate business logic/data from UI code as much as I can, but that only helps mitigate the risk. The risk is still there, the cost of swapping UI frameworks is just smaller. Even XUL and FLEX have lock-in. Laszlo seems to have a plan for using Flash and .NET runtimes, which is a good thing.

What I am thinking about is creating a bare-bones DHTML desktop application runtime using MSHTML (for starters). Much of the needed functionality is already there. I have already posted a couple entries on using MSHTML to augment your Win32 or .NET application. The big difference now is that MSHTML would actual host your application. Think of it as a specialized web browser. Instead of browsing web pages, the primary function would be hosting DHTML applications. Whatever functionality is not already present could be added (generically) using MSHTML extension interfaces.

If rich Internet applications like Oddpost can be built, surely kick-ass desktop applications are possible as well.

OOD – Less Is More

My primary professional development language has been C++ for as long as I remember. It still is and I have no strong desire to move to something else. That also means that, many years ago, I embraced the Object Oriented Design (OOD) methodology.

I have derived Cat and Dog from Animal many times. At first, it took a while to see the object hierarchies, but it became easier. I loved object hierarchies. The deeper, the better.

Then, I started COM programming and designing using interfaces (abstract base classes) seemed to free my mind of the implementation details. I was happy. I was using interfaces for non-COM code in no time.

Then, I started using templates. First, with the help of the STL. What a concept! Later, with the help of Modern C++ Design (Andrei Alexandrescu) and the Boost libraries. Implementing design patterns using templates was amazing. Templates allowed me to do things I thought only inheritance could. I am glad I was wrong. Suddenly, my object hierarchies are not as deep.

Then, I started to learn XML and Services. Not really programming, I know, but the concept of storing all that data in a document that could be queried so easily made me question all those class getters and setters. Even some kinds of inheritance seemed overkill. Why can’t classes be more behavorial and less stateful? Let XML be the state and just pass it around.

In the end, I’m still using objects. Just less of them. I guess the data is becoming more important than the code.

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);