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.

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.

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

MSHTML Hosting – IDocHostUIHandler

We can’t discuss embedding WebBrowser in an application without also discussing IDocHostUIHandler. The IDocHostUIHandler (and IDocHostShowUI) interface is the standard method provided by Microsoft for customizing how WebBrowser works when hosted in an application. Some of the things you can use IDocHostUIHandler to do include:

  • Disable standard right-click, context menu (or provide your own version)
  • Turn off the 3D border and scrollbars
  • Give the WebBrowser scripting engine access to your special purpose COM methods
  • Handle accelerator keys and URL’s before MSHTML gets them

IDocHostUIHandler is an interface that you need to implement. How you implement depends on your development language and environment. For most languages, it means deriving a class from IDocHostUIHandler, adding code to the methods you want and returning safe, default values from those you do not want.

The next step is giving MSHTML your implementation of IDocHostUIHandler. Again, this depends on your development language and environment. The easy way is the “ICustomDoc” method. The best way is the “IOleClientSite” method. There is a nice C# article on CodeProject that discusses both methods (as well as some other good WebBrowser information).

ICustomDoc

This method requires only IDocHostUIHandler to be implemented which keeps things simpler. However, you have to give the interface to MSHTML after each page is loaded. The best way to do it is in the OnDocumentComplete or OnNavigationComplete events. This is the method I currently use in my applications. Code looks something like this:


IHTMLDocument2* pDoc = ...;
ICustomDoc* pCustom = 0;
hr = pDoc->QueryInterface(IID_ICustomDoc, (void**)&pCustom);
if (SUCCEEDED(hr)) {
  pCustom->SetUIHandler(pMyDocHostUIHandler);
  pCustom->Release();
}

IOleClientSite

This method requires that you implement IOleClientSite, as well as IDocHostUIHandler. The benefit is that you only need to give the interfaces to MSHTML once, before any HTML is loaded. I have not used this method in my applications, so I have no code to show you. I am planning on using it soon. I’ll update this post when I am finished.

I am switching from ICustomDoc to IOleClientSite mainly because of a transient problem. When the WebBrowser control loads my custom HTML, it waits until it is completely loaded before getting my IDocHostUIHandler, so there is a split second where the borders and scrollbars are not displaying correctly. With IOleClientSite, WebBrowser asks for my IDocHostUIHandler before it loads the HTML.

MSHTML Hosting – Odds & Ends

In this post I wanted to cover some miscellaneous things you may want to do with your embedded WebBrowser. On its own, the IWebBrowser2 interface does not support doing much more than we already covered in previous posts. However, if you start
using the MSHTML DOM interfaces, much more functionality is available. Here is a list of simple things you can implement without too much difficulty:

  • Retrieving HTML from the WebBrowser.
  • Retrieving the HTML of the current selection.
  • Finding text in the HTML and selecting it.
  • Creating an image of the current HTML.

Retrieving HTML from the WebBrowser

There are times when you might want to get the currently loaded HTML from the control. You may want to save it to a file or parse it for information. For this functionality, you have to use the IPersistXxx interfaces. These are the same we used to load HTML into the WebBrowser from memory. The same works in reverse:


IHTMLDocument2* pDoc = ...;
IStream* pMyStream = ...;

IPersistStreamInit* pPersist = 0;
HRESULT hr = pDoc->QueryInterface(IID_IPersistStreamInit, (void**)&pPersist);
if (SUCCEEDED(hr) && pPersist) {
    hr = pPersist->Save(pMyStream, true);
    pPersist->Release();
}

Retrieving the HTML of the current selection

If you want to limit the HTML to just what a user has selected, instead of the entire document, we can use the IHTMLXxx COM interfaces. The first thing you need to do is get access to the IHTMLDocument interface for the current document. IWebBrowser2 gives you access using it’s Document property. The Document property returns an IDispatch interface, so we need to QueryInterface the IDispatch interface for an IHTMLDocument interface, like so (raw C++):


IDispatch* pDocDisp = 0;
HRESULT hr = pWebBrowser->get_Document(&pDocDisp);

IHTMLDocument2* pDoc = 0;
hr = pDocDisp->QueryInterface(IID_IHTMLDocument2, (void**)&pDoc);
if (SUCCEEDED(hr)) {

    //...

    pDoc->Release();
}

pDocDisp->Release();

The IHTMLXxx interfaces follow the W3C DOM specification used for JavaScript very closely. If your familiar with those objects, the IHTMLXxx interface will be easy to grasp. In fact, if you know how to do something using JavaScript, you can duplicate it your compiled code using the IHTMLXxx interfaces.

That said, you can get the current selection as a IHTMLTxtRange from the document element. Once you have a text range, you can retrieve the plain text or HTML text as shown below:


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;
         pTextRange->get_text(&sText);
         // or
         pTextRange->get_htmlText(&sText);
         //...
         pTextRange->Release();
      }
      pDispRange->Release();
   }
   pSelection->Release();
}

pDoc->Release();

Finding text in the HTML and selecting it

The Google toolbar in IE does this to make it easy to spot keywords found in the page. We are using body and text range objects. This time we are making a IHTMLTxtRange object, not getting the current selection. IHTMLTxtRange has find and select methods that make this task easy. Be sure to check out the parameters for IHTMLTxtRange::findText as they can be used to modify how the text is searched:


IHTMLDocument2* pDoc = ...;
IHTMLElement* pBodyElem = 0;
HRESULT hr = pDoc->get_body(&pBodyElem);
if (SUCCEEDED(hr)) {
   IHTMLBodyElement* pBody = 0;
   hr = pBodyElem->QueryInterface(IID_IHTMLBodyElement, (void**)&pBody);
   if (SUCCEEDED(hr)) {
      IHTMLTxtRange* pTextRange = 0;
      hr = pBody->createTextRange(&pTextRange);
      if (SUCCEEDED(hr)) {
         CComBSTR sText = "findme";
         VARIANT_BOOL bSuccess;
         hr = pTextRange->findText(sText, 0, 0, &bSuccess);
         if (SUCCEEDED(hr) && bSuccess == VARIANT_TRUE)
            pTextRange->select();
         pTextRange->Release();
      }
      pBody->Release();
   }
   pBodyElem->Release();
}

pDoc->Release();

Creating an image of the current HTML

Turning the contents of the WebBrowser into an image is not as straight forward as you may expect. Looking at the IHTMLXxx interfaces does turn up an IHTMLElementRenderer interface. IHTMLElementRenderer contains:

IHTMLElementRender::DrawToDC(HDC hDC);

You can try to use this method, but I have found that it is not very reliable and reacts inconsistently depending on the type of HDC you give it. A more reliable method uses an older OLE method. IViewObject supports the ability to render to an HDC. The IWebBrowser2::Document property can be QueryInterfaced for IViewObject. Two things to note while using this method, (1) you will probably want to turn off the scrollbars and 3D border since they will show up in the image and (2) you will want to resize the WebBrowser to the size of the contained HTML if you want to capture the entire content in the image. You may want to only make these changes temporarily and change them back after the image is captured:


IHTMLDocument2* pDoc = ...;
IHTMLElement* pBodyElem = 0;
HRESULT hr = pDoc->get_body(&pBodyElem);
if (SUCCEEDED(hr)) {
   IHTMLBodyElement* pBody = 0;
   hr = pBodyElem->QueryInterface(IID_IHTMLBodyElement, (void**)&pBody);
   if (SUCCEEDED(hr)) {
      // hide 3D border
      IHTMLStyle* pStyle;
      hr = pBodyElem->get_style(&pStyle);
      if (SUCCEEDED(hr)) {
         pStyle->put_borderStyle(CComBSTR("none"));
         pStyle->Release();
      }

      // hide scrollbars
      pBodyElement->put_scroll(CComBSTR("no"));

      // resize the browser component to the size of the HTML content
      IHTMLElement2* pBodyElement2;
      hr = Body->QueryInterface(IID_IHTMLElement2, (void**)&BodyElement2)
      if (SUCCEEDED(hr)) {
         long iScrollWidth = 0;
         pBodyElement2->get_scrollWidth(&iScrollWidth);

         long iScrollHeight = 0;
         pBodyElement2->get_scrollHeight(&iScrollHeight);

         // these lines depend on your WebBrowser wrapper
         pWebBrowser->SetWidth(iScrollWidth);
         pWebBrowser->SetHeight(iScrollHeight);

         pBodyElement2->Release();

         IViewObject* pViewObject;
         pDoc->QueryInterface(IID_IViewObject, (void**)&pViewObject);
         if (pViewObject) {
            /* however you want to make your image HDC.
               You can size it using iScrollHeight & iScrollWidth */
            HDC hImageDC = ... // could be bitmap or enhanced metafile
            HDC hScreenDC = ::GetDC(0);
            RECT rcSource = {0, 0, iScrollWidth, iScrollHeight};
            hr = pViewObject->Draw(DVASPECT_CONTENT, 1, NULL, NULL,
                                   hScreenDC, hImageDC, rcSource,
                                   NULL, NULL, 0);
            ::ReleaseDC(0, hScreenDC);
            pViewObject->Release();
         }
      }
      pBody->Release();
   }
   pBodyElem->Release();
}

pDoc->Release();

As you can see, there is a lot of things you can do using the MSHTML object model. Some of it can be tricky. Other things just aren’t supported as well as they should be for an application developer. I guess you could say that application developers have their own list of issues for IE.

MSHTML Hosting – Mozilla

Before moving ahead with the MSHTML hosting posts, I wanted to take a moment to talk about alternatives to WebBrowser. I am sure you are familiar with the Mozilla webbrowser. Started as an open source project by Netscape, Mozilla and its suite of companion projects are quite an achievement. One of the Mozilla projects is an ActiveX wrapper around the rendering engine which conforms to the IWebBrowser2 and DWebBrowserEvents2 COM interfaces. Created and maintained by Adam Lock the project has come along way. There is even minimal support for the IHTMLDocument DOM interfaces.

Everything we have covered in previous posts regarding WebBrowser functionality can be implemented using the Mozilla control as well. With no code changes! Just embed the Mozilla control and your application will not be dependent on Microsoft. Some of us like having choices.

My biggest problem with the Mozilla control is that the project is not moving fast enough to implement more functionality. Honestly, if Mozilla wants to grab more market share, it should be putting more resources on the project. One or two guys is not enough. I know that some people would tell me to just use the native Mozilla C++ classes and interfaces to embed the rendering engine. I am sorry, but there is too much to learn. A very large number of people know, and are comfortable using, ActiveX controls and COM data types. Frameworks have been built to make it easy to use such controls. Why would I want to learn a niche framework? That’s the main reason I have not been able to contribute to the project myself. Its a large investment.

That said, I still love the idea of having choices. The Mozilla control is a great start and if I was writing an application that only need basic features, I would seriously consider using it. I hope very much that the project keeps progressing.

More…

Nick Bradury (author of FeedDemon and TopStyle) on Mozilla control.

Joel Spolsky (of Joel On Software) on why Mozilla should be actively building an ActiveX wrapper.

MSHTML Hosting – Building UI’s

My last posts have dealt with using the WebBrowser component to display HTML pages inside your application. We have seen how it does not take much to embed the control to create your own little mini-webbrowser. In this post I want to go a little further than building a webbrowser. Lots of applications are using a web-like UI. Applications like Intuit Quicken, Microsoft Outlook and Money actually use the WebBrowser control to achieve their web-like UI’s. Microsoft Office task panes and other inductive UI’s can be developed using the WebBrowser control.

When using the WebBrowser control for building a UI there are a few issues to consider:

  1. Loading the HTML into the control. You most likely will not be using Navigate to load the HTML. In most cases you are dynamically generating the HTML from scratch or from a template loaded from a resource.
  2. Handling mouse and keyboard events. Since you are building a UI, it is very likely that the content will be interactive. There may be edit boxes, push buttons and hyperlinks in the content. You will need to handle those events and respond accordingly.

Loading HTML

The most common (but not the only) way to load HTML from a buffer to the WebBrowser is via streams. Microsoft has a nice example in the WebBrowser reference. A quick Google search will turn up a way to do it in your favorite tool or language. The key points here are:

  1. Navigate to “about:blank”
  2. Wait for the blank document to finish loading
  3. Load your HTML using the IPersistStreamInit method

Step #3 looks like this:


IHTMLDocument2* pDoc = ...;
IStream* pMyStream = ...;

IPersistStreamInit* pPersist = 0;
HRESULT hr = pDoc->QueryInterface(IID_IPersistStreamInit, (void**)&pPersist);
if (SUCCEEDED(hr) && pPersist) {
    hr = pPersist->InitNew();
    if (SUCCEEDED(hr)) {
        hr = pPersist->Load(pMyStream);
    }
    pPersist->Release();
}

Note: I would strongly recommend that any external links you have in your generated HTML (stylesheets or JavaScript) are referenced using absolute paths. Do not use relative paths. The IPersistStreamInit method does not update the control’s base URL. Navigate does update the base URL. Therefore, any relative path will have “about:blank” prepended to it and the control will not likely find your external link.

Handling Events

The ability to handle mouse and keyboard events is critical when creating an interactive UI. There are 2 basic methods to do this with WebBrowser:

  1. Use <A> tags to create hyperlinks with bogus href URL’s. Then use OnBeforeNavigate2 to intercept the bogus URL, cancel the navigation and respond to the mouse click.
  2. Hook your native code to the onclick, onkeypress, or any of the many other JavaScript events.

Method #1 is cheap and easy, but really only works with hyperlinks. In my applications, I create bogus hyperlinks that are easy to parse inside OnBeforeNavigate2 and contain breadcrumbs for me to use when responding to the click. Here is an example:

myapp://edititem/12345

I can look for a constant substring to indicate its my bogus HREF (myapp://). I can figure out the type of action (edititem). I also know which item to edit (12345). The last part could be a database ID or a pointer to an object cast to a long integer. The whole thing even looks like a real URL too.

Method #2 is much more robust, but a little more complicated to implement. There are more steps involved. You will also be working with the IHTMLDocument system and creating IDispatch wrappers. We’ll cover those topics in future posts.

MSHTML Hosting – The Basics

Hosting IE in your application is a relatively straight forward process, provided your development environment supports the use of ActiveX controls. Each language/framework has its own way of doing it: VB works directly with the WebBrowser control, MFC has its CHtmlView wrapper classes, Delphi has the TWebBrowser wrapper and C++Builder uses TCppWebBrowser. Create one of these somewhere in your application and your on your way to displaying HTML pages.

Before I go any further, I want to point you to the MSDN documentation of reusing the WebBrowser control. It will be an invaluable reference to you.

The WebBrowser control is really made up of a command interface (IWebBrowser2) and event interfaces (DWebBrowserEvents and DWebBrowserEvents2). Unless you are writing code against the raw control (please don’t), your wrapper component will expose both of these sides to you automatically. The event names may be slightly different between components. Here are the most useful methods and events:

  • Document – This property is your means to gain access to the IHTMLDocument2 MSHTML interface. More on this in later posts, I just wanted to point it out now.
  • Navigate / Navigate2 – Provides a simple way to tell the WebBrowser to display a page from a given file or URL. Remember to specify the full URL (including http://). Navigate is the simpler method. Both support functionality such as passing in flags to keep the page from displaying in IE’s cache list.
  • GoHome / GoBack / GoForward / Refresh – Allow you to mimic the IE functionality with the respective names.
  • ExecWB – Provides a way to get the WebBrowser to execute commands (listed here), such as Print, Print Preview, Save As, Copy and Find.
  • OnBeforeNavigate2 – Event that is called before the WebBrowser actually navigates to a given page. This event allows you to cancel or redirect the navigation. Many embedded browser applications use this event to implement “custom protocols” where clicking on a link will display your dialog, for example.
  • OnDocumentComplete – Event that is called when a page is fully loaded into the browser. Use this event as a trigger for hooking up other functionality that can only be done after a page is completely in the browser.
  • OnNavigateComplete2 – Event that is fired as individual pieces of the page are loaded. Many people assume this event will only be called once per page load. Not true, it is called once for each frame and then for the page. It is usually safer to use OnDocumentComplete, unless you need to be notified for each frame.

Using these methods and events, it is very easy to create a nicely featured web browser. Next time we can look at ways to make WebBrowser seem less like a web browser and more like a custom HTML display control you can use inside your application.