C++ Q&A

Finding a Win32 Handle, HTML in CHtmlCtrl

Paul DiLascia

Code download available at: CQA0308.exe (1,261 KB)
Browse the Code Online

Q I encountered a problem using your suggested approach to find the Internet Explorer_Server window as described in your September 2001 column. When using GetLastChild with an HTML page containing comboboxes, sometimes it returns the combobox instead of the Microsoft® Internet Explorer window. You can see in Spy++ that comboboxes are child windows of the Internet Explorer window, but sometimes OnNavigateComplete2 was called before these windows were created, which my program didn't account for. I changed the GetLastChild function to check the class name of the child window. If an Internet Explorer_Server window is found, it returns it. Do you know a better solution?

Q I encountered a problem using your suggested approach to find the Internet Explorer_Server window as described in your September 2001 column. When using GetLastChild with an HTML page containing comboboxes, sometimes it returns the combobox instead of the Microsoft® Internet Explorer window. You can see in Spy++ that comboboxes are child windows of the Internet Explorer window, but sometimes OnNavigateComplete2 was called before these windows were created, which my program didn't account for. I changed the GetLastChild function to check the class name of the child window. If an Internet Explorer_Server window is found, it returns it. Do you know a better solution?

Michael Theis

A Indeed I do, but first let me briefly remind everyone what we're talking about. In the January 2000 issue of Microsoft Systems Journal I showed how to modify the MFC CHtmlView class, which can only live inside a CFrameWnd, to remove the frame dependencies and create a new class called CHtmlCtrl that works in a dialog or any other kind of window.

A Indeed I do, but first let me briefly remind everyone what we're talking about. In the January 2000 issue of Microsoft Systems Journal I showed how to modify the MFC CHtmlView class, which can only live inside a CFrameWnd, to remove the frame dependencies and create a new class called CHtmlCtrl that works in a dialog or any other kind of window.

Then in my September 2001 column, I showed how to disable the (browser) context menu for CHtmlCtrl by subclassing the Internet Explorer_Server window. The window that actually displays the HTML is not the browser (CHtmlView/CHtmlCtrl) window, but a great-grandchild with the class name "Internet Explorer_Server". I presented the function GetLastChild that returns the "last child" of a window. That is, GetLastChild returns the child of the child of the child... until there are no more children. This assumes a window hierarchy where each window has only one child, and the last descendant is the Internet Explorer window. Usually this is correct, but (as Michael found out the hard way) not if the document has children like comboboxes. Oops. I should've tested my code against some more complex HTML, but all I was trying to do was implement a simple About dialog. (Note that in Internet Explorer, edit controls and buttons are not child windows as you might expect.)

Figure 1 shows a class that implements a better way to obtain the Internet Explorer window. CFindWnd finds the first child window of any window with a given class name. In order to use it, all you have to do is write:

CFindWnd ies(m_hWnd, "Internet Explorer_Server");
myHwndIE = ies.m_hWnd;

Figure 1 FindWnd.h

////////////////////////////////////////////////////////////////
// MSDN Magazine -- August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
// •••
// This class encapsulates the process of finding a window with a given 
// class name as a descendant of a given window. To use it, instantiate 
// like so:
//
//    CFindWnd fw(hwndParent,classname);
//
// fw.m_hWnd will be the HWND of the desired window, if found.
//
class CFindWnd {
private:
   //////////////////
   // This private function is used with EnumChildWindows to find the 
   // child with a given class name. Returns FALSE if found (to stop 
   // enumerating).
   //
   static BOOL CALLBACK FindChildClassHwnd(HWND hwndParent, 
      LPARAM lParam) {
      CFindWnd *pfw = (CFindWnd*)lParam;
      HWND hwnd = FindWindowEx(hwndParent, NULL, pfw->m_classname, NULL);
      if (hwnd) {
         pfw->m_hWnd = hwnd;  // found: save it
         return FALSE;        // stop enumerating
      }
      // recurse
      EnumChildWindows(hwndParent, FindChildClassHwnd, lParam); 
      return TRUE;            // keep looking
   }

public:
   LPCSTR m_classname;        // class name to look for
   HWND m_hWnd;               // HWND if found

   // ctor does the work—just instantiate and go
   CFindWnd(HWND hwndParent, LPCSTR classname)
      : m_hWnd(NULL), m_classname(classname)
   {
      FindChildClassHwnd(hwndParent, (LPARAM)this);
   }
};

The constructor calls a function that uses EnumChildWindows and FindWindowEx to search all descendant windows until it exhausts them or finds one whose class name matches the one requested. You can use FindWindow to find top-level windows, but if you want to search children, you need FindWindowEx, which was first introduced in Win32®. CFindWnd returns the first window that matches, so it's only useful for finding windows that you expect to have only one instance of. In general, when searching for special windows, it's always safest to check the class name.

But—as another reader, Domenico Belgiorno pointed out—you don't even need to subclass the Internet Explorer window to disable the context menu. You can do it completely from within CHtmlCtrl, like so:

BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg)
{
  if (pMsg->message == WM_CONTEXTMENU)
    return TRUE; // eat it
  return CHtmlView::PreTranslateMessage(pMsg);
}

This works because MFC implements a very ingenious and powerful feature. In the main message pump inside CWinThread, MFC calls a function CWnd::WalkPreTranslateTree. This function loops through all the parent windows of the window for which the message is destined, calling PreTranslateMessage for each one and stopping if any parent window's PreTranslateMessage returns TRUE. The upshot is that you can override PreTranslateMessage to intercept messages sent to your window's descendants. Very clever!

Experience reveals that to make the previous code snippet work as desired, you must also trap WM_RBUTTONDOWN and WM_RBUTTONDBLCLK, and you should also check to make sure the target window's class name is in fact Internet Explorer_Server so you don't accidentally trap some other child window's context menu (unless that's what you want). Figure 2 shows the final code for CHtmlCtrl::PreTranslateMessage. I enhanced it so it can be configured through a property Get/SetHideContextMenu, and to send a WM_CONTEXTMENU message to the parent window instead of eating it. This lets you implement your own context menu if you like. Note that CHtmlCtrl sends WM_CONTEXTMENU when the right mouse button goes up, not down.

Figure 2 CHtmlCtrl::PreTranslateMessage

HtmlCtrl.h

////////////////////////////////////////////////////////////////
// MSDN Magazine -- August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#pragma once

//////////////////
// This struct defines one entry in the command map that maps text 
// strings to command IDs. If your command map has an entry "about" that 
// maps to ID_APP_ABOUT, and your HTML has a link <A HREF="app:about">, 
// then clicking the link will invoke the ID_APP_ABOUT command. To set 
// the map, call CHtmlCtrl::SetCmdMap.
//
struct HTMLCMDMAP {
   LPCTSTR name;     // command name used in "app:name" HREF in 
                     // <A UINT nID;
};

//////////////////
// Class to turn CHtmlView into ordinary control that can live in dialog 
// as well as frame.
//
class CHtmlCtrl : public CHtmlView {
protected:
   HTMLCMDMAP* m_cmdmap;   // command map
   BOOL m_bHideMenu;       // hide context menu

public:
   CHtmlCtrl() : m_bHideMenu(FALSE), m_cmdmap(NULL) { }
   ~CHtmlCtrl() { }

   // get/set HideContextMenu property
   BOOL GetHideContextMenu()         { return m_bHideMenu; }
   void SetHideContextMenu(BOOL val) { m_bHideMenu=val; }

   // Set doc contents from string
   HRESULT SetHTML(LPCTSTR strHTML);

   // set command map
   void SetCmdMap(HTMLCMDMAP* val)   { m_cmdmap = val; }

   // create control in same place as static control
   BOOL CreateFromStatic(UINT nID, CWnd* pParent);

   // create control from scratch
   BOOL Create(const RECT& rc, CWnd* pParent, UINT nID,
      DWORD dwStyle = WS_CHILD|WS_VISIBLE, 
          CCreateContext* pContext = NULL)
   {
      return CHtmlView::Create(NULL, NULL, dwStyle, rc, pParent,
          nID, pContext);
   }

   // override to intercept child window messages to disable context menu
   virtual BOOL PreTranslateMessage(MSG* pMsg);

   // Normally, CHtmlView destroys itself in PostNcDestroy, but we don't 
   // want to do that for a control since a control is usually 
   // implemented as a member of another window object.
   //
   virtual void PostNcDestroy() {  }

   // Overrides to bypass MFC doc/view frame dependencies. These are
   // the only places CHtmView depends on living inside a frame.
   afx_msg void OnDestroy();
   afx_msg int  OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, 
      UINT msg);

   // override to trap "app:" pseudo protocol
   virtual void OnBeforeNavigate2( LPCTSTR lpszURL,
      DWORD nFlags,
      LPCTSTR lpszTargetFrameName,
      CByteArray& baPostedData,
      LPCTSTR lpszHeaders,
      BOOL* pbCancel );

   // You can override this to handle "app:" commmands.
   // Only necessary if you don't use a command map.
   virtual void OnAppCmd(LPCTSTR lpszCmd);

   DECLARE_MESSAGE_MAP();
   DECLARE_DYNAMIC(CHtmlCtrl)
};

HtmlCtrl.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
// --
// Implementation for CHtmlCtrl — Web browser in a control. Overrides
// CHtmlView so you don't need a frame—can use in dialog or any other 
// kind of window.
//
// Features:
// - SetCmdMap lets you set a command map for "app:command" links.
// - SetHTML lets you set document contents (HTML) from string.

#include "StdAfx.h"
#include "HtmlCtrl.h"

// macro to declare a typedef for ATL smart pointers; eg SPIHTMLDocument2
#define DECLARE_SMARTPTR(ifacename) typedef CComQIPtr<ifacename> SP##ifacename;

// smart pointer to IHTMLDocument2
DECLARE_SMARTPTR(IHTMLDocument2)

// useful macro for checking HRESULTs
#define HRCHECK(x) hr = x; if (!SUCCEEDED(hr)) { \
   TRACE(_T("hr=%p\n"),hr);\
   return hr;\
}

... // same as earlier version

// Return TRUE if hwnd is Internet Explorer window.
inline BOOL IsIEWindow(HWND hwnd)
{
   static LPCSTR IEWNDCLASSNAME = "Internet Explorer_Server";
   char classname[32]; // always char, never TCHAR
   GetClassName(hwnd, classname, sizeof(classname));
   return strcmp(classname, IEWNDCLASSNAME)==0;
}

//////////////////
// Override to trap "Internet Explorer_Server" window context menu 
// messages.
//
BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg)
{
   if (m_bHideMenu) {
      switch (pMsg->message) {
      case WM_CONTEXTMENU:
      case WM_RBUTTONUP:
      case WM_RBUTTONDOWN:
      case WM_RBUTTONDBLCLK:
         if (IsIEWindow(pMsg->hwnd)) {
            if (pMsg->message==WM_RBUTTONUP)
               // let parent handle context menu
               GetParent()->SendMessage(WM_CONTEXTMENU, pMsg->wParam, 
                  pMsg->lParam);
            return TRUE; // eat it
         }
      }
   }
   return CHtmlView::PreTranslateMessage(pMsg);
}

//////////////////
// Override to pass "app:" links to virtual fn instead of browser.
//
void CHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL,
   DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData,
   LPCTSTR lpszHeaders, BOOL* pbCancel )
{
   const char APP_PROTOCOL[] = "app:";
   int len = _tcslen(APP_PROTOCOL);
   if (_tcsnicmp(lpszURL, APP_PROTOCOL, len)==0) {
      OnAppCmd(lpszURL + len);          // call virtual handler fn
      *pbCancel = TRUE;                 // cancel navigation
   }
}

//////////////////
// Called when the browser attempts to navigate to "app:foo". Default 
// handler searches command map for "foo" command, and sends parent a 
// WM_COMMAND message with the ID if found. Call SetCmdMap to set the 
// command map. Only override OnAppCmd if you want to do something more 
// complex.
//
void CHtmlCtrl::OnAppCmd(LPCTSTR lpszCmd)
{
   if (m_cmdmap) {
      for (int i=0; m_cmdmap[i].name; i++) {
         if (_tcsicmp(lpszCmd, m_cmdmap[i].name) == 0)
            // Use PostMessage to avoid problems with exit command. (Let
            // browser finish navigation before issuing command.)
            GetParent()->PostMessage(WM_COMMAND, m_cmdmap[i].nID);
      }
   }
}

//////////////////
// Set document contents from string
//
HRESULT CHtmlCtrl::SetHTML(LPCTSTR strHTML)
{
   HRESULT hr;

   // Get document object
   SPIHTMLDocument2 doc = GetHtmlDocument();

   // Create string as one-element BSTR safe array for 
   // IHTMLDocument2::write.
   CComSafeArray<VARIANT> sar;
   sar.Create(1,0);
   sar[0] = CComBSTR(strHTML);

   // open doc and write
   LPDISPATCH lpdRet;
   HRCHECK(doc->open(CComBSTR("text/html"),
      CComVariant(CComBSTR("_self")),
      CComVariant(CComBSTR("")),
      CComVariant((bool)1),
      &lpdRet));
   
   HRCHECK(doc->write(sar));  // write contents to doc
   HRCHECK(doc->close());     // close
   lpdRet->Release();         // release IDispatch returned

   return S_OK;
}

Q I'm using your CHtmlCtrl in my dialog-based app. Since I use it to show HTML pages that I create at run time, I would like to know if there's any way to show HTML content without first storing it in a file. Formatting an HTML string and storing it in a file simply for displaying it in the CHtmlCtrl seems tedious.

Q I'm using your CHtmlCtrl in my dialog-based app. Since I use it to show HTML pages that I create at run time, I would like to know if there's any way to show HTML content without first storing it in a file. Formatting an HTML string and storing it in a file simply for displaying it in the CHtmlCtrl seems tedious.

Joan Murt Llum

A Setting the HTML from a text string is fairly straightforward, even if the mechanics are a bit tedious in C++. If you've ever written any JavaScript, you know you can call document.write any time to write HTML directly into the document as your page is loading. The same is true in C++, but the coding is messy because you have to deal with COM and IHTMLDocument2, BSTRs, and SAFEARRAYs. Fortunately, the ActiveX® Template Library (ATL) has a number of classes that make it a breeze.

A Setting the HTML from a text string is fairly straightforward, even if the mechanics are a bit tedious in C++. If you've ever written any JavaScript, you know you can call document.write any time to write HTML directly into the document as your page is loading. The same is true in C++, but the coding is messy because you have to deal with COM and IHTMLDocument2, BSTRs, and SAFEARRAYs. Fortunately, the ActiveX® Template Library (ATL) has a number of classes that make it a breeze.

Figure 3 HtmlApp Displays HTML On the Fly

Figure 3** HtmlApp Displays HTML On the Fly **

Figure 3 shows a new program, HtmlApp, modified from the AboutHtml programs in my January 2000 and September 2001 columns (see previous question) to display an HTML list of top-level windows in its main view. The original AboutHtml program showed how to implement an "about" dialog in HTML, and loaded it from a resource by calling:

m_page.LoadFromResource(_T("about.htm"));

CHtmlView::LoadFromResource opens the URL res://program.exe/about.htm, where program.exe is the actual name of your program. The res: protocol lets you open any HTML file stored as a resource. But what if, instead of loading a resource, you want to generate HTML on the fly—as HtmlApp does when displaying its list of top-level windows? As Joan points out, it's silly to save your HTML to disk, only to immediately read it back. To set the document contents directly, I added a new function, CHtmlCtrl::SetHTML (see Figure 2).

Let me take you through it step by step. First, you have to get the IHTMLDocument2 interface:

SPIHTMLDocument2 doc = GetHtmlDocument();

SPIHTMLDocument2 is equivalent to CComQIPtr<IHTMLDocument2>, an ATL smart pointer to an IHTMLDocument2 interface. (For those few programmers still using COM, if you're not using ATL smart pointers, get with the program!) Next, you have to create a SAFEARRAY that holds your HTML string as a one-element BSTR array. SAFEARRAY is a COM structure for passing arrays safely between platforms. ATL provides CComBSTR and CComSafeArray classes to take the ouch! out of working with BSTRs and safe arrays:

// strHTML is LPCTSTR
CComSafeArray<VARIANT> sar;
sar.Create(1,0);
sar[0] = CComBSTR(strHTML);

Without CComSafeArray and CComBSTR, this would be 10 to 20 lines of grody code with calls to API functions like SafeArrayCreateVector, SafeArrayAccessData, and SafeArrayUnaccessData. You should feel thankful to the ATL folks that you don't have to write that stuff.

Once you have your doc object and contents in a safe array, you're ready to open the doc, write to it, and close it. IHTMLDocument2::write requires VARIANTS and BSTRs, but once again ATL comes to the rescue:

LPDISPATCH lpdRet;
doc->open(CComBSTR("text/html"),  // MIME type
  CComVariant(CComBSTR("_self")), // open in same window
  CComVariant(CComBSTR("")),      // no features
  CComVariant((bool)1),           // replace history entry
  &lpdRet));                      // IDispatch returned
doc->write(sar); // write it
doc->close();    // close
lpdRet->Release();

CHtmlCtrl::SetHTML is pretty handy. There's just one trick to using it: when you first create your CHtmlCtrl, it has no document (GetHtmlDocument returns NULL). So before you call SetHTML, you need to create one. The simplest way is to open a blank document, as shown in the following line of code:

m_wndView.Navigate(_T("about:blank"));

And by the way, if your HTML is simple enough, you can use about: instead of CHtmlCtrl::SetHTML to get your HTML, as shown in the following code snippet:

m_wndView.Navigate(_T
("about:<HTML><B>hello, world</B>
</HTML>"));

This works for simple HTML, but for more complex documents you need SetHTML. HtmlApp builds some HTML on the fly with an IMG, TABLE, and links, to list the visible top-level windows, then calls SetHTML to display it. Figure 3 shows HtmlApp running; Figure 4 shows the code.

Figure 4 HtmlApp

HtmlApp.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
// •••
// HtmlApp shows how to implement an About dialog in HTML using a new
// class, CHtmlCtrl, that lets you use CHtmlView as a control in a
// dialog or any other kind of window. HtmlApp also shows how to set
// HTML from a string and how to disable the browser's context menu.
//

#include "StdAfx.h"
#include "resource.h"
#include "MainFrm.h"
#include "HtmlCtrl.h"
#include "TraceWin.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

class CMyApp : public CWinApp {
public:
   virtual BOOL InitInstance();
protected:
   afx_msg void OnAppAbout();
   DECLARE_MESSAGE_MAP()
} theApp;

BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
   ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
END_MESSAGE_MAP()


BOOL CMyApp::InitInstance()
{
   // Create main frame window (don't use doc/view stuff)
   CMainFrame* pMainFrame = new CMainFrame;
   if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
      return FALSE;
   pMainFrame->ShowWindow(m_nCmdShow);
   pMainFrame->UpdateWindow();
   m_pMainWnd = pMainFrame;

   return TRUE;
}


//////////////////
// About dialog uses HTML control to display contents.
//
class CAboutDialog : public CDialog {
protected:
   CHtmlCtrl m_page;       // HTML control
   virtual BOOL OnInitDialog(); 
public:
   CAboutDialog() : CDialog(IDD_ABOUTBOX, NULL) { }
   DECLARE_DYNAMIC(CAboutDialog)
};
IMPLEMENT_DYNAMIC(CAboutDialog, CDialog)


//////////////////
// init about dialog
//
BOOL CAboutDialog::OnInitDialog()
{
   // cmd map for CHtmlCtrl handles "app:ok"
   static HTMLCMDMAP AboutCmds[] = {
      { _T("ok"), IDOK },
      { NULL, 0  },
   };
   VERIFY(CDialog::OnInitDialog());
   VERIFY(m_page.CreateFromStatic(IDC_HTMLVIEW, this)); // create HTML 
                                                        // ctrl
   m_page.SetHideContextMenu(TRUE);                     // hide context 
                                                        // menu
   m_page.SetCmdMap(AboutCmds);                         // set command 
                                                        // table
   m_page.LoadFromResource(_T("about.htm"));            // load HTML from 
                                                        // resource
   return TRUE;
}


//////////////////
// run about dialog
//
void CMyApp::OnAppAbout()
{
   static CAboutDialog dlg; // static to remember state of hyperlinks
   dlg.DoModal();           // run it
}

Figure 4 HtmlApp

MainFrm.h

////////////////////////////////////////////////////////////////
// MSDN Magazine — August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//

#include "HtmlCtrl.h"

/////////////////
// Typical main frame...
//
class CMainFrame : public CFrameWnd {
public:
   CMainFrame(){ }
   virtual ~CMainFrame() { }
protected:
   CHtmlCtrl   m_wndView;               // CHtmlCtrl as main view
   CStatusBar  m_wndStatusBar;          // status line
   CToolBar    m_wndToolBar;            // toolbar

   afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
   afx_msg void OnContextMenu(CWnd* pWnd, CPoint pos);

   // helper to format main window HTML
   CString FormatWindowListHTML();

   DECLARE_DYNCREATE(CMainFrame)
   DECLARE_MESSAGE_MAP()
};

Figure 4 HtmlApp

MainFrm.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — August 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#include "StdAfx.h"
#include "resource.h"
#include "MainFrm.h"
•••
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
   ON_WM_CREATE()
   ON_WM_CONTEXTMENU()
END_MESSAGE_MAP()

// Commmand map for app: commands in main window HTML.
HTMLCMDMAP MyHtmlCmds[] = {
   { _T("about"), ID_APP_ABOUT },
   { _T("exit"),  ID_APP_EXIT  },
   { NULL, 0  },
};

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   VERIFY(CFrameWnd::OnCreate(lpCreateStruct)==0);

•••

   // create/init html control as view
   VERIFY(m_wndView.Create(CRect(), this, AFX_IDW_PANE_FIRST));
   m_wndView.SetHideContextMenu(TRUE);          // hide context menu
   m_wndView.SetCmdMap(MyHtmlCmds);             // set commands
   m_wndView.Navigate(_T("about:blank"));       // create document
   m_wndView.SetHTML(FormatWindowListHTML());   // set contents from 
                                                // string
   SetActiveView(&m_wndView);                   // set as active view for 
                                                // MFC

   return 0;
}

//////////////////
// Handle context menu command.
// Don't do anything; just display TRACE to prove it was called.
//
void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint pos)
{
   TRACE(_T("CMainFrame::OnContextMenu\n"));
}

////////////////////////////////////////////////////////////////
// Following stuff builds the HTML displayed in the main view

////////////////
// EnumWindows callback proc: if window is visible, add info to HTML 
// table.
//
static BOOL CALLBACK MyEnumWindowsProc(HWND hwnd, LPARAM lp)
{
   DWORD style = GetWindowLong(hwnd, GWL_STYLE);
   if (style & WS_VISIBLE) {
      CString& s = *(CString*)lp;
      char cname[256];
      GetClassName(hwnd, cname, sizeof(cname));
      TCHAR text[1024];
      GetWindowText(hwnd, text, sizeof(text)/sizeof(text[0]));
      CString temp;
      temp.Format(_T("<TR><TD>%p </TD><TD>%s</TD><TD>%s</TD></TR>\n"),
         hwnd, cname, text);
      s += temp;
   }
   return TRUE;
}

//////////////////
// This function builds a text string that is the HTML document for the
// main window.
//
CString CMainFrame::FormatWindowListHTML()
{
   // start w/top matter
   CString html = _T("<HTML><BODY STYLE=\"font-family:Verdana;\" \
link=\"#02B7B7\" vlink=\"#02B7B7\">\n\
<TABLE WIDTH=\"100%\">\n\
<TR><TD VALIGN=top><A target=\"new\" HREF=\"https://msdn.microsoft.com/msdnmag\">
<IMG BORDER=0 ALT=\"MSDN Magazine\" SRC=\"res://HtmlApp.exe/msdn.gif\"></A>
</TD>\
<TD COLSPAN=2><B>HtmlApp — List of visible top-level windows</B><BR>\n\
<SMALL>Written by <A target=\"new\" \
HREF=\"https://paul.dilascia.com\">Paul DiLascia</A></SMALL></TD></TR>\n\
<TR><TD><B>hwnd</B></TD><TD><B>class</B></TD><TD WIDTH=75%><B>title</B>
</TD><TR>\n");

   // enumerate top-level windows to append their info
   EnumWindows(MyEnumWindowsProc, (LPARAM)&html);

   // append bottom matter. note commands app:about and app:exit
   html += _T("</TABLE>\n\
<P><B>[<A HREF=\"app:about\">About</A>]  \
[<A HREF=\"app:exit\">Exit</A>]</B>\n\
</BODY>\n\
</HTML>");

   return html;
}

Finally, let me point out a totally unrelated but really neat new feature I added for HtmlApp to make CHtmlCtrl even more useful. Back in the January 2000 issue, I showed you how to implement an "app:" pseudo-protocol that lets you create HTML links (anchor elements) that communicate with your program. For example, if you add a link

<A HREF="app:about">About</A>

then CHtmlCtrl::OnBeforeNavigate2 will recognize "app:" and call the special virtual function CHtmlCtrl::OnAppCmd with "about" as parameter. You can make up your own commands and override OnAppCmd in a derived class to process them. After using CHtmlCtrl for a while, I soon found myself frequently deriving from CHtmlCtrl just to override this one function. What a waste of typing! To spare my fingers, I invented a simple notion of command maps that let you convert "app:command" to a normal Windows® WM_COMMAND ID:

HTMLCMDMAP MyHtmlCmds[] = {
  { _T("about"), ID_APP_ABOUT },
  { _T("exit"),  ID_APP_EXIT  },
  { NULL, 0  },
};

To use this map, call CHtmlCtrl::SetCmdMap:

m_wndHtmlCtrl.SetCmdMap(MyHtmlCmds);

Now when the user clicks the link to "app:about", CHtmlCtrl::OnAppCmd searches your command map, finds the "about" entry, and sends its parent window a WM_COMMAND message with ID_APP_ABOUT as the ID—whereupon the command enters the magic MFC command-routing superhighway, where any window along the road can handle the command. Pretty sweet! HtmlApp uses this feature to add About and Exit commands directly to the main window as HTML links (see Figure 3). Figure 2 shows the details. The only trick is that CHtmlCtrl::OnAppCmd sends the command via PostMessage instead of SendMessage because of the fact that you'll find yourself in trouble if you try to close the application from the midst of OnBeforeNavigate2. (I found out the hard way.)

Happy programming!

Send your questions and comments for Paul to  cppqa@microsoft.com.

Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at https://www.dilascia.com.