MSDN Magazine > Issues and Downloads > 2001 > September >  C++ Q&A;: Disabling Context Menus, Sending Comm...
C++ Q&A;: Disabling Context Menus, Sending Commands to Doc Objects
MSDN Magazine
Disabling Context Menus, Sending Commands to Doc Objects
Paul DiLascia
Download the code for this article: CQA0109.exe (1,040KB)
Browse the code for this article at Code Center: AboutHtml and DOCTIME

Q
I liked your implementation of CHtml-Ctrl in a dialog. It is exactly what I wanted my app to do. But there's one thing I want to know. Is there a way I can disable the popup menu that results when the user right-clicks on the HTML page? I don't want the user to right-click and view the source of the HTML file I display in my app. I tried overriding WM_CONTEXTMENU in my CHtmlCtrl-derived window, but it doesn't work.
Aamir

I would like to suppress the right button context menu and the popup menu of the CHtmlView or CHtmlCtrl classes. I have tried handling WM_RBUTTONDOWN, but that does not work. Please help.
Gerhard Koell
Austria

A
I'm glad you both liked my CHtmlCtrl class from the January 2000 issue of Microsoft Systems Journal. For readers who didn't catch it, CHtmlCtrl is a class that converts CHtmlView into a control you can use in any window. I used it to write AboutHtml, a program that implements an HTML About dialog. But as Aamir points out, I overlooked something: if you right-click in the dialog box, you get the standard browser context menu (see Figure 1), which is probably not what you want. Well, put a cone over my head and call me Dunce!

Figure 1 Unwanted Context Menu
Figure 1 Unwanted Context Menu

      Fortunately, the problem is trivial to fix. You don't even have to write any code! Just add a line in your HTML page:
<BODY oncontextmenu="return false">
This tells the browser: don't display a context menu. You could also write
oncontextmenu="ShowMyMenu(); return false"
where ShowMyMenu is a JavaScript procedure that displays your own custom menu. I wrote a new program, AboutHtml1, that uses oncontextmenu; you can download it from the link at the top of this article.
      But since this is a C++ column, not a JavaScript column, let me show you another, more complicated way to accomplish the same thing using C++. Why not? The official C++ way would be to implement IDocHostUIHandler (see the docs for more info), but that's a lot of work. Your idea of handling WM_CONTEXTMENU or WM_RBUTTONDOWN in CHtmlCtrl is on the mark, and certainly the normal Windows way to do things. The problem is, the CHtmlCtrl window isn't the real input window. There are more windows here than meet the eye. To see how many, just open up your trusty Spy++ tool and take a look. As Figure 2 shows, the browser window is three parent/child levels above the actual input window.
Dialog
 AfxFrameOrView42d      // CHtmlCtrl
  Shell Embedding
   Shell DocObject View
    Internet Explorer_Server
      It's the Internet Explorer_Server window that receives input, and that's the window you have to subclass if you want to trap WM_CONTEXTMENU. In MFC, this means you have to get the HWND and call SubclassWindow. Keep in mind, of course, that this is highly irregular, grody, and definitely verboten as far as the Redmondtonians are concerned. Nevertheless, I wrote yet another version of the original program, AboutHtml2, that does it.

Figure 2 Parent/Child Relationships in Spy++
Figure 2 Parent/Child Relationships in Spy++

      There are many ways to get the mysterious Internet Explorer_Server HWND. FindWindow doesn't work since it only gets top-level windows. Since the server window is the great-grandchild of the browser, with no siblings at any level, the following algorithm works.
static HWND GetLastChild(HWND hwndParent)
{
   HWND hwnd = hwndParent;
   while (TRUE) {
      HWND hwndChild = ::GetWindow(hwnd, GW_CHILD);
      if (hwndChild==NULL)
         return hwnd;
      hwnd = hwndChild;
   }
   return NULL;
}
      This function assumes a single-child-only descendant chain like the one in the browser window—that is, each parent has exactly one child—and gets the "last" (youngest?) child window, which in this case is the Internet Explorer_Server window. Once you have the HWND, all you have to do is write a new MFC class to subclass it.
class CMyIEWnd : public CWnd {
public:
   afx_msg void OnContextMenu(CWnd* pWnd, CPoint pos) { }
   DECLARE_MESSAGE_MAP();
};
This class overrides WM_CONTEXTMENU to do nothing: OnContextMenu is an empty function that returns without displaying a menu or calling the base class (CWnd). To use CMyIEWnd, I added an instance in CMyHtmlCtrl:
class CMyHtmlCtrl : public CHtmlCtrl {
protected:
   CMyIEWnd m_myIEWnd;
};
      All that's needed to hook everything up is to call SubclassWindow. But where? Or rather, when? The most convenient time is after the browser has loaded your page.
void CMyHtmlCtrl::OnNavigateComplete2(LPCTSTR strURL)
{
   if (!m_myIEWnd.m_hWnd) {
      HWND hwnd = GetLastChild(m_hWnd);
      m_myIEWnd.SubclassWindow(hwnd);
   }
}
      So now it goes like this: when the user invokes the About dialog, the dialog creates the CHtmlCtrl window to open the document; after the browser has opened the document, it sends a notification which MFC directs to OnNavigateComplete2. CMyHtmlCtrl::OnNavigateComplete2 calls GetLastChild to obtain the "real" input window and subclasses it. Now all messages destined for Internet Explorer_Server go through CMyIEWnd, including WM_CONTEXTMENU. Be warned, however: the Internet Explorer HWND can change, so if you do this for something more than About dialogs, you must make provisions to unsubclass and resubclass the HWND.
      There are two important things to note about this technique. First, it's powerful since now that you've subclassed the "real" Internet Explorer window, you can do almost anything you want. Second, it's really, really, really bad, and likely to get you in trouble if you aren't careful. Once you take over the Explorer window like this, all bets are off. Didn't your mother tell you that you're not supposed to subvert the browser? You're supposed to customize it through the official interfaces (IDocHostUIHandler)! But there's nothing like breaking the taboos to make your blood flow.

Q
I'm writing a Windows® 2000 performance-monitor-like application in which I have a doc object and several views. The doc object is responsible for collecting data regularly, and notifies the associated views that render the data in different formats. In order for the doc to regularly collect data, it needs a time event. However, the doc is not a window-based object, so it cannot have such a time event. I'm weighing three possible solutions:
  1. Create a timer in one of the views. When the event happens, it tells the doc object to get data.
  2. Create a separate thread in/for each doc, so that the thread can create time events regularly.
  3. Create a timer in CMainFrame, and call doc(s) function from it.
      I don't like any of these options. Can you please give me a better solution to this problem?
Lei Hu
Australia

A
Of the options you list, putting the timer in the view is a bad idea because then you'd have one timer for each view, and you should think of timers as a relatively finite resource. (This was especially true in the old days, less so now.) Creating separate threads is overkill for such a simple thing as a timer. Threads are sure to make your life complicated and unhappy. That leaves option number three: create a timer in the main frame, and call the docs from there. Let me show you how to do this cleanly, and then I'll show you another option you haven't considered.
      I assume the reason you don't like the third option is that it requires calling the documents from your main frame, which is kind of ugly. (Why should the frame know anything about the documents?) But there's a clean way to do it. Instead of calling CMyDoc::DoTimerThing directly, you can convert the WM_TIMER message to a WM_COMMAND with some ID like ID_APPTIMER, and broadcast this command in the normal way so that documents can handle it using ON_COMMAND. Documents can't handle all windows messages, but they can handle WM_COMMAND. In fact, this is one of the main innovations of the MFC command-routing architecture, that it lets non-window objects handle commands. So it would appear all you have to do is the following:
CMainFrame::OnTimer(...)
{
   SendMessage(WM_COMMAND, ID_APPTIMER);
}
      That is, when your main frame gets a timer click, have it send an ID_APPTIMER command to itself. MFC will route the command through the system, and any object that has an ON_COMMAND handler for ID_APPTIMER can then process the event. You can use ON_COMMAND_EX so multiple objects can handle the same event.
      This works, except for one problem. MFC only routes commands to the active view/document. If other documents are open but not active, they won't get the WM_COMMAND message. You can fix this by modifying your app so it broadcasts commands to inactive documents too, but then ordinary commands like File | Save would get routed to all the documents—oops! Only the timer command should go to all documents. How do you do that? How do you send a WM_COMMAND to all documents?
      The way MFC sends commands to non-window objects like documents is through the virtual function CCmdTarget::OnCmdMsg. When your window gets a WM_COMMAND message, it goes through lots of CWnd code and virtual functions. Eventually, control arrives at CWnd::OnCommand, which calls OnCmdMsg.
// in CWnd::OnCommand
OnCmdMsg(nID, CN_COMMAND, NULL, NULL);
Here, nID is the command ID and CN_COMMAND is a code that tells OnCmdMsg that this is a command event—as opposed to, say, an update UI event (in which case the code would be CN_UPDATE_COMMAND_UI). The extra parameters aren't used for CN_COMMAND.
      So the upshot is: if you have a pointer to a document and you want to send that document a command, all you have to do is call
pDoc->OnCmdMsg(nID, CN_COMMAND, NULL, NULL);
This is totally general in the sense that whoever makes this call doesn't have to know anything about the document, except that it is a document. In fact, pDoc needn't be a document at all; it could point to any CCmdTarget-derived object. In effect, OnCmdMsg is the function that CWnd uses to convert WM_COMMAND (for windows) to CN_COMMAND (for command targets).
      Knowing this, you can now solve your problem. If you put the timer in your main frame (CMainFrame), you can write a handler that passes the WM_TIMER event to all the docs like so:
CMainFrame::OnTimer(...)
{
   while (pDoc = /* each document */) {
      pDoc->OnCmdMsg(ID_APPTIMER, 
         CN_COMMAND, NULL, NULL);
   }
}
      How do you loop through all the documents? Figure 3 shows the boilerplate code. Since I can never remember that stuff, nor how MFC's wacky POSITIONs work, I long ago wrote a little class for iterating documents, called—what else—CDocIterator. I have less trouble remembering how CDocIterator works.
for (CDocIterator it; it.doc(); it++) {
   it.doc()->DoSomething();
}
      Figure 4 shows my CDocIterator class. The constructor initializes a CPtrList with all the documents, using the code from Figure 3; the other functions navigate this list. Ho-hum. I wrote a little program, DocTimer1, that puts everything together. CMainFrame::OnCreate sets a one-second timer, and CMainFrame::OnTimer uses CDocIterator to send ID_APPTIMER to all the docs.
for (CDocIterator it; it.doc(); it++) {
   it.doc()->OnCmdMsg(ID_APPTIMER, 
      CN_COMMAND, NULL, NULL);
}
      The documents handle ID_APPTIMER the normal way, using ON_COMMAND. Notice that the _EX version isn't required because CMainFrame::OnTimer ignores the return code from OnCmdMsg, passing the command to each doc regardless of whether it was handled or not. The handler CMyDoc::OnAppTimer increments a counter and updates the view. In real life, you'd do whatever you have to do.
      Figure 5 shows DocTimer1 running with a bunch of documents open. You'll have to take my word for it that all the documents update themselves every second—or download the source and compile and run it yourself.

Figure 5 DocTimer1
Figure 5 DocTimer1

      If you really don't like your main frame calling the documents and managing the timer, there is yet another way, perhaps the cleanest way to implement a timer of this sort. Namely, you can use a timer proc instead of WM_TIMER. Normally when you set a timer, you supply an HWND (or implicit CWnd for CWnd::SetTimer) that identifies the window that should receive WM_TIMER messages. But you can pass NULL as the HWND and instead supply a procedure that Windows should call.
void WINAPI MyTimerProc(HWND hwnd, 
  UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
   •••
}
Now if you call
SetTimer(NULL, 0, 1000, MyTimerProc);
Windows will call your timer proc directly instead of sending WM_TIMER. (You still need a main window, however, or at least a Get/DispatchMessage loop—that's where Windows checks the timer.) You can use the timer proc to remove CMainFrame entirely from the picture. You could even implement your doc timer feature entirely within your document class; all you need is a timer proc and a few static globals. Figure 6 shows a document class that implements its own timer using the previously described CDocIterator to iterate all the documents. Of course, if you implement everything in your doc class, the timer proc could call CMyDoc::DoSomething directly, instead of converting the timer pop to a command event.
      There's one more twist on the timer proc approach, and this is my favorite. Conceptually, the timer is a global service that any object might want to use. You should be able to implement some kind of timer object you can just plop into your app, and then different objects could "listen" to it. How could you implement such a beastie?
      It's easy. All you have to do is modify the document-centric approach just outlined in two ways. First, put the timer proc and SetTimer/KillTimer calls in a separate CAppTimer class that manages the timer; and second, instead of broadcasting ID_APPTIMER to all documents, broadcast it to an arbitrary list of command targets. All you need is a way for objects to add and remove themselves from the list.
      DocTime2 implements this approach. DocTime2 uses two new classes: CCmdTargetList (see Figure 7) and CAppTimer (see Figure 8). CCmdTargetList is a generic list o' command targets with functions that an object can call to register and unregister (add and remove) themselves from the list, and a SendCommand function that broadcasts a command ID to every object on the list. CAppTimer is derived from CCmdTargetList, since the timer is, among other things, a list of command targets. CAppTimer manages the timer and provides a single global instance, theTimer, with an Init function the app must call to set the timer.
// from CMyApp::InitInstance
theTimer.Init(1000, ID_APPTIMER);
This tells CAppTimer to create a 1000 millisecond (1 second) timer and use ID_APPTIMER as the command ID. Each document that wants to receive timer notifications now simply registers itself with the timer when the document is constructed, and unregisters when the document is destroyed.
CMyDoc::CMyDoc()
{
   theTimer.Register(this);
}
CMyDoc::~CMyDoc()
{
   theTimer.Unregister(this);
}
      What could be simpler? The great thing about this approach is that it encapsulates the timer in a single class. All you have to do is plop it into your project, call Init, and then let each object decide for itself whether or not it cares to listen to the clock. Documents, views, frames—any command target—can register for timer pops, and then receive notification through the normal ON_COMMAND mechanism. Once again, there's no need to use ON_COMMAND_EX because CCmdTargetList::SendCommand ignores the return code from OnCmdMsg. This makes sense: no object should be able to prevent other objects from receiving notifications. No matter what, the beat goes on.

Send 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 askpd@pobox.com or http://www.dilascia.com.

From the September 2001 issue of MSDN Magazine.


Page view tracker