MSDN Magazine > Issues and Downloads > 2001 > June >  C++ Q&A;: Browser Detection Revisited, Fixing C...
From the June 2001 issue of MSDN Magazine.
MSDN Magazine
Browser Detection Revisited, Fixing CPopupText, COM and the IService Provider Interface
Paul DiLascia
Download the code for this article: CQA0106.exe (60KB)
Browse the code for this article at Code Center: LCTest Update

A
ttention! We interrupt this column for another brief episode in the continuing saga of the default browser... In the January 2001 issue, Rolf Wenger asked how to find the default Internet browser. I wrote:
...the full mysteries of the registry are deeper than any individual can probe, even supposed experts. For all I know, there could be a key somewhere, HKCU\System\Mumble\Bletch\Blah\Gak\DefaultBrowser. If you know of such a key, please write in."
      Well, as I knew would happen, someone wrote in. Mike, aka "Hyperion," from Turin, Italy, pointed out that you can find the default browser in
HKCR\http\shell\open\command = 
 "C:\PROGRA~1\INTERN~1\iexplore.exe" -nohome
Congratulations, Mike! You win the gold star. This is one step simpler than my solution, which was to look at the HKCR\.htm="htmlfile" key, then
HKCR\htmlfile\shell\open\command =
 "C:\PROGRA~1\INTERN~1\iexplore.exe" -nohome
      Figure 1 shows some other registry keys for various Internet protocols. I figured I should pass them along for posterity. Just remember: only use the registry key if you really want to know which browser is the default—for example, if you�re a market researcher or corporate spy. If all you want to do is open a Web page or HTML file, call ShellExecute(Ex).
      Also of note is the key
HKLM\Software\Clients\Mail\(Default) = "Microsoft Outlook"
or Outlook® Express, or presumably whatever mail program you might be using. Then look under
HKLM\Software\Clients\Mail\Microsoft Outlook\shell\open\command =
  "OUTLOOK.EXE" Outlook:Inbox /recycle
or whatever your default mail program is. In general, it appears that HKLM\Software\Clients\Foo is the name of the subkey to use for Foo things; HKLM\Software\Clients\Foo\subkey tells you what actual program to use. Figure 2 shows some of the other interesting keys in HKLM\Software\Clients. These entries correspond to the items in Control Panel | Internet Options or View | Internet Options, on the Programs tab (see Figure 3).

Figure 3 Internet Options
Figure 3 Internet Options

      Naturally, all this is highly undocumented, subject to change, risky, and could deleteriously affect your mental well-being. Besides, it has nothing to do with C++.

Q
Your work on CPopupText in the September 2000 issue of MSDN Magazine was great, but in your test app—LCTest with the listbox—if you click on items with tooltips, they cannot be selected.
Udo Gerber
Germany

A
What?! My program has a bug?! You mean to imply I am fallible? I shall send someone to take care of you immediately! Meanwhile, let me draw my mighty programming hammer and smash the pesky pest before anyone else finds out!
      Well, it�s true. In the September 2000 program, Mihai Frintu asked how to display a popup tip that shows the full text of a listbox item if the item is too long to fit. I wrote a program, LCTest (see Figure 4), that does it, except for the problem Udo so kindly points out. (Don�t worry, his life is in no danger.)

Figure 4 Displaying a Popup Tip
Figure 4 Displaying a Popup Tip

      LCTest uses a special class, CListBoxTipHandler, to trap messages sent to the listbox. CListBoxTipHandler is derived from CSubclassWnd, a class I use in many of my columns to intercept messages sent to another window. (For more info, see the original column or download the source at http://msdn.microsoft.com/msdnmag/issues/0900/c/c0900.asp.) CListBoxTipHandler uses another class, CPopupText, to display the item text if it�s too long to fit. Unfortunately, as Udo discovered, when the text is displayed as in Figure 4, there�s no way to select the item under it. How embarrassing!
      When the user clicks on the popup text in Figure 4, you want Windows® to act as if the text were nonexistent, invisible, kaput. That is to say, void. You want the mouse event to pass through the text and go directly to the listbox below. Do not pass go, do not collect $200. Lucky for me, Windows has a way to arrange just that.
      When a user clicks his mouse somewhere on the screen, Windows squirrels through its internal lists to determine which one, if any, lies beneath the cursor. But before sending that window WM_LBUTTONDOWN, Windows first sends WM_NCHITTEST to find out which non-client area the cursor is over. If the cursor lies over the caption, the window returns HTCAPTION. If the cursor is in the menu, the window returns HTMENU. And if it�s in the client area, the window returns HTCLIENT. It may seem perplexing to call the client area a non-client area, but you get the idea. Most apps never handle WM_NCHITTEST—in fact, you probably never even heard of it 'til now—because the default window procedure, DefWindowProc, handles it for you. DefWindowProc does all the coordinate calculations to figure out whether the pixel is in the caption, menu, borders, sizebox client area, whatever—and serves up the appropriate HT code.
      One of the codes a window can return is HTTRANSPARENT. Mmm, very interesting. This tells Windows, "I�m transparent. Don�t send me any mouse events. Pass them to the next guy." Meaning, of course, the next window under the cursor in Z-order for the window�s thread. So here�s all that�s required to fix CPopupText:
UINT CPopupText::OnNcHitTest(CPoint pt)
{
  return HTTRANSPARENT;
}
      That�s it, bug smashed. Now when users click the text in Figure 4, the listbox item is selected. Amazing! I love bugs that are fixed by adding a one-line function. They prove your original design was, if not perfect, at least not bad.
      But now there�s another problem: When the user clicks the text, there�s no feedback. The item is selected, but there�s no way to tell unless you have X-ray vision. Basic Rule Number One of UI design is: when something happens, let the user know. It�s very disconcerting to click the mouse and wonder: is it or is it not selected?
      To give the user some feedback, all that�s needed are a few more lines in CListBoxTipHandler::WindowProc, the function that traps messages sent to the listbox.
LRESULT CListBoxTipHandler::WindowProc(...)
{
    if (msg==WM_LBUTTONDOWN) {
    g_wndTip.Cancel(); // kill text
    break;
  }
  return CSubclassWnd::WindowProc(msg, wp,          lp);
}
Now when the user selects an item, CListBoxTipHandler traps the event and kills the popup text, if any, to reveal the newly selected item in its full highlighted glory, as Figure  5 shows.

Figure 5 No Popup Tip
Figure 5 No Popup Tip

      And while I�m fixing bugs.... The original CPopupText used the default status font (NONCLIENTMETRICS::lfStatusFont). It seems better to use the same font as the listbox (duh), so I made that enhancement as well. You can download the fully updated LCTest2 from the link at the top of this article. Aren�t you glad I�m not too proud to admit my mistakes?

Q
I use the Active Accessibility SDK to obtain an IHTMLDoc-ument2 pointer from a window handle (HWND). Is there any way to obtain an IWebBrowser2 pointer from the IHTMLDocument2 pointer? I�ve tried QueryInterface on both the document pointer as well as the IHTMLWindow2 pointer without results. I also tried the IOmNavigator * from get_navigator on the HTMLWindow2 pointer. Any ideas?

A
This is a common cause for COM consternation. You have the window, document, or browser and you know you should be able to get the others, but everywhere you turn, QueryInterface delivers a big fat NULL. The answer lies in the mysterious IServiceProvider whose job it is, well, to provide services. IServiceProvider is a great interface: it has only one method, QueryService. If you�re using ATL smart pointers, it looks like this. First, you have to get the IServiceProvider.
CComQIPtr<IServiceProvider> isp = pIHTMLDocument2;
This does a QueryInterface on the document for IServiceProvider. Once you have it, you can get the browser like so.
CComQIPtr<IWebBrowser2> iwb2;
isp->QueryService(IID_IWebBrowserApp,
    IID_IWebBrowser2, (void**)&iwb2);
      If all this seems confusing, there�s a good reason for it. A cardinal rule of COM is that QueryInterface must always return an interface to the object queried. But the document doesn�t implement IWebBrowser2; it only knows how to get the object that does. The document, browser, and window are all separate objects. In general, IServiceProvider is used whenever a bunch of separate but related COM objects together implement some kind of service. QueryInterface asks an object, do you implement this interface? QueryService tells a service provider, "get me whatever object implements this interface, please." With QueryService, the interface pointer returned may or may not be the same object as the one queried. Figure 6 illustrates the system. All the objects implement their various interfaces and store internal pointers to one another; IServiceProvider is your way to get whichever object implements a particular interface. IServiceProvider::QueryService chases the internal pointers to retrieve the object that implements the interface you want.

Figure 6 Many Objects, One IServiceProvider
Figure 6 Many Objects, One IServiceProvider

      IServiceProvider is essential for navigating the DHTML object hierarchy. Say you�re writing an ActiveX® control and you want to navigate the object model. How do you do it? First, query your IOleClientSite for IServiceProvider like so:
CComQIPtr<IServiceProvider> isp = pSite;
then, once you have IServiceProvider, you can QueryService it for the application object.
CComQIPtr<IWebBrowserApp> iwba;
isp->QueryService(IID_IWebBrowserApp, 
    IID_IWebBrowserApp, (void **)&iwba); 
Now you can navigate the object hierarchy (the app object is at the top). If you want the Web browser, it�s the same as before.
CComQIPtr<IWebBrowser2> iwb2;
isp->QueryService(IID_IWebBrowserApp, 
 IID_IWebBrowser2, (void **)&iwb2)); 
      In all these examples, SID_SWebBrowserApp identifies the service, but you�ll often see code that uses IID_IWebBrowserApp as the service ID. Either will work, since <shlguid.h> #defines SID_SWebBrowserApp to IID_IWebBrowserApp, but for programming pedants SID_SWebBrowserApp is technically more correct, and more clear to anyone reading your code.
      By the way, if you�re brave enough to implement some far-reaching colossal object system such as the DHTML object model (Lord help you!), then you should implement IServiceProvider too.

Got a question? Send questions and comments 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.

Page view tracker