Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > December 1997
December 1997

Microsoft Systems Journal Homepage

C++ Q&A

Code for this article: Cqa.exe (22KB)
Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992).

Q In the September 1997 issue you wrote that code like this is technically incorrect:

CMyDialog::SomeFn( ... )
{   
    CEdit* pEdit = (CEdit*)GetDlgItem(ID_EDIT1); // NOT!   
    pEdit->GetSel( ... );
}
But we've seen this type of code used in many books as a way to access a control. Is there a procedure that would make the above sample technically correct—that is, a procedure other than using DDX/DDV or subclassing the control (which I believe would create a permanent map entry)?
Doug Kehn and Ray Marshall

A There is another way, but not one that avoids creating a permanent map entry. First, let me quickly remind those of you who didn't read the September issue why the cast is incorrect. It's because GetDlgItem returns a CWnd, not a CEdit. You can easily see this by using MFC runtime classes or C++ dynamic casting. I wrote a little program called EditCast (see Figure 1) that's essentially a dialog with a button in it. When you press the button, EditCast executes the following code:

 CWnd*  pWnd  = GetDlgItem(IDC_EDIT1);
 CEdit* pEdit = dynamic_cast<CEdit*>(pWnd);
 BOOL   bIsEdit = pWnd->IsKindOf(RUNTIME_CLASS(CEdit));
Figure 2 TraceWin Utility
Figure 2 TraceWin Utility

Then it prints the values of pWnd, pEdit, and bIsEdit in TRACE diagnostics. Figure 2 shows the output in my TraceWin utility. As you can see, doing a C++ dynamic cast to CEdit fails (the cast returns NULL); likewise, CObject::IsKindOf returns FALSE, indicating that the pointer does not point to a CEdit-derived object. And yet, following the above lines of code, EditCast next executes these lines, which work perfectly fine:


 pEdit = (CEdit*)pWnd;
 pEdit->SetSel(0, -1);
 pEdit->ReplaceSel("");
 pEdit->SetFocus();
When you press the button, the code deletes whatever was in the edit control. So what gives?
As I explained in September, the code works because the CEdit functions are merely thin wrappers that send messages to the window. For example, ReplaceSel sends an EM_REPLACESEL to the control:

 // in afxwin2.inl
 inline void 
 CEdit::ReplaceSel(LPCTSTR lpsz, BOOL bCanUndo)
 { 
     ::SendMessage(m_hWnd, EM_REPLACESEL, 
                   (WPARAM)bCanUndo, (LPARAM)lpsz); 
 }
Since the control really is an edit control, it responds to EM_ REPLACESEL by doing what you expect. The only reason it works is that CEdit contains no data members or virtual functions. If ReplaceSel had been a virtual function instead of inline, calling it would call the CWnd function, not the CEdit one, no matter how you cast. C++ would call through the function pointer in the vtable, which would be CWnd::ReplaceSel. And if ReplaceSel used some data member that was part of CEdit and not CWnd, the code would crash or do something unpredictable because the CWnd object would not have this data.
So what's the correct way to avoid the cast? As you point out, the normal thing to do is put a CEdit object in your dialog and then subclass it:

 class CMyDialog : public CDialog {
     CEdit m_edit;
     virtual BOOL OnInitDialog() {
         m_edit.SubclassDlgItem(IDC_EDIT, this);
         return CDialog::OnInitDialog();
     }
 •
 •
 •
 };
Now if you call GetDlgItem, you get a CEdit object since m_edit is in the permanent map. Remember, CWnd::GetDlgItem only creates a CWnd on-the-fly if it can't find one in the permanent map. Using a dialog object is the preferred way to access controls, but there are times when this may not be feasible. Say you're writing some library code and didn't create the edit control, but had it passed to you. In that case, you can access the edit control like so:

 CMyDialog::SomeFn( ... )
 {
     CEdit edit;
         edit.Attach(::GetDlgItem(m_hWnd, IDC_EDIT));
         edit.SetSel( ... );
 •
 •
 •
         edit.Detach();
 }
In this example, ::GetDlgItem (the Windows API function, not the CWnd function) returns the HWND of the control and I attach it to a local CEdit object. CWnd::Attach adds the CEdit object to the permanent map and sets CEdit.m_hWnd to the HWND of the edit control. Just make sure you don't forget to Detach the control when you're done—otherwise the CWnd destructor will destroy the actual control. Of course, this trick will fail if the control is already attached to a CWnd-derived object.
If you want to be absolutely safe, you could write something like this:

 HWND hwnd = GetDlgItem(IDC_EDIT);
 CEdit* pEdit =  
     CWnd::FromHandlePermanent(hwnd);
 BOOL bMine;
 if (pEdit) {
     ASSERT_KINDOF(CEdit, pEdit);
 } else {
     pEdit = new CEdit;
     pEdit->Attach(hwnd);
     bMine = TRUE;
 }
 •
 •
 •
 if (bMine) {
     pEdit->Detach();
     delete pEdit;
 }
CWnd::FromHandlePermanent only returns a CWnd pointer if the window has a permanent object attached to it. If not, I create a CEdit and attach it. In general, this technique can be used whenever you're given an HWND from somewhere that you know is an edit control (or static, or button) and you want to treat it as such.
I often use the local variable technique to program GDI. For example, there are many messages and callbacks where Windows
® gives you a device context in the form of an HDC. To access it with MFC, you can write:

  CDC dc;
  dc.Attach(hdc);
  dc.SelectObject(...);
  •
  •
  •
  dc.Detach();
This is the equivalent of writing:

 CDC& dc = *CDC::FromHandle(hdc);
The difference is that you save a memory allocation since the CDC object is a stack variable. On the other hand, if you use FromHandle you don't have to worry about detaching since MFC takes care of detaching and destroying the temporary CDC object during its idle processing. In general, it's probably safer to use FromHandle since it returns the permanent object if there is one. This applies to all MFC objects—windows, device contexts, brushes, and so on.

Q I'm writing an MFC app with an About dialog that shows the logo, name, address, and URL of my company. I thought it would be a neat feature to make the URL a hotlink so the user can click on it and the dialog would launch the browser and go to that page. I figured out how to get the name of the default browser from the system registry by looking up the file association for .htm or .html, but it seems like a lot of work. Plus, the browser always starts a new instance instead of using the current one if the browser is already open. There has to be some easy way to do this, but I've looked through all the manuals and I can't find anything. Please help!

Lloyd Kemske

A You'd think that with all the Web hype and hysteria, and with Microsoft touting Windows 98 and its new browser desktop model, and with practically every app under the sun having some kind of Internet function, there'd be some obvious API function like OpenThisHereURLForMeNowPlease. Well, there is, but it's not obvious which function you'll want to use. As far as I can tell, there's no mention anywhere in the documentation that this incredibly useful function, which can open any file on your desktop, can also be used to open Internet URLs. The only reference I could find was an obscure note in the Microsoft® Access KnowledgeBase.
The magic function is ShellExecute, the replacement for WinExec. You can feed it the name of any file and ShellExecute will figure out how to open it. For example, this code opens the file liza.bmp (oo-la-la) in your default bitmap editor, which might be Microsoft Paint, Adobe Photoshop, or Corel PhotoPaint:


 ShellExecute(NULL, "open", "liza.bmp", NULL, NULL, 
              SW_SHOWNORMAL);
I won't bore you by describing all the arguments; you can read the documentation yourself. The important thing is that ShellExecute lets you open any file you want. It even knows how to open desktop (.lnk) and URL (.url) shortcuts. ShellExecute parses all the registry gobbledygook in HKEY_ CLASSES_ROOT to figure out what app to run and whether to fire up a new instance or feed the file name to an already open instance using DDE. Either way, ShellExecute returns the HINSTANCE of the app that opened the file.
But the neat thing is that ShellExecute doesn't just open files on your computer, it opens files on the Internet too.

 ShellExecute(NULL, "open", "http://nbc.com",
              NULL, NULL, SW_SHOWNORMAL);
This code takes you to the NBC home page, where you can find out all about "Homicide" episodes. When ShellExecute sees http: at the front of the file name, it scratches its head and says, "Gee, I think maybe this is a Web file," and branches off into some code to launch Microsoft Internet Explorer or Netscape Navigator or whatever browser you're using to open the file. ShellExecute recognizes other protocols too, like FTP and gopher. (Does anyone ever use gopher?) It even recognizes mailto, so if you feed it the file name mailto:askpd@pobox.com, it'll fire up your mail program and open a new message addressed to yours truly. In short, ShellExecute provides one-stop shopping to open any file on a disk or the Internet. It will use any means necessary to try to open whatever string you give it. You can also use it to print files or explore a folder—just pass print or explore as the command. There's also a ShellExecuteEx variant with so many parameters it has a special struct to hold them all. For more information, read the docs.
By the way, I should point out that the Windows 95 START command has the same capability. START lets you start any file—including URLs—from an MS-DOS
® prompt:

 start foo.bmp
 start http://nbc.com
START is useful for old command-line hackers like me who still like to write batch files that call awk and sed.
In any case, once you know the secret of ShellExecute, it's fairly easy to add a link to your About dialog. In fact, I wrote a class, CStaticLink (see Figure 3), that makes it trivial. CStaticLink converts any static control into a hyperlink. To use it, all you have to do is create a static control in your dialog, then hook it up to your dialog by calling SubclassDlgItem from your dialog's InitDialog function, just as you would to subclass any other kind of control.

 class CMyDialog : public CDialog {
     CStaticLink m_link;
     virtual BOOL OnInitDialog() {
         m_link.SubclassDlgItem(IDC_MYURL, this);
         return CDialog::OnInitDialog();
     }
 •
 •
 •
 };
Figure 4 shows a program I wrote that uses CStaticLink to implement the About dialog in Figure 5. The dialog has two links: clicking on my URL takes you to my home page; clicking the MSJ icon takes you to the MSJ Web site.
Figure 5 About Dialog
Figure 5 About Dialog

A static control can be either text or graphic, and I wrote CStaticIcon to work in either case. For text controls, CStaticLink gets the URL from the window text (GetWindowText); for graphic controls you have to set the public CString member m_link to the URL you want to link to. You can also use m_link with a text control if you want the displayed text to be different from the hyperlink. For example, you might want the text to read "Email: ziggy@godthaab.com", but the URL would be "mailto:ziggy@godthaab.com".
As an added touch, CStaticLink draws the text using an underline font in blue (unvisited) or purple (visited) in the case of a text control, as per the browser's GUI guidelines. To change the font and text color, CStaticLink uses MFC message reflection to handle its own WM_CTLCOLOR message. Naturally, you can choose other colors if you like—just change m_colorVisited and m_colorUnvisited.
OnCtlColor also makes sure the control has the SS_NOTIFY style.
Normally, static controls don't get mouse messages like WM_LBUTTONDOWN. That's because the Windows default window proc for static controls returns HTTRANSPARENT in response to WM_NCHITTEST, which makes the control transparent to mouse events; if you click the mouse on a static control, Windows feeds the message to the parent, not the control. You can write a WM_LBUTTONDOWN handler for a static control and sit there all day waiting for someone to call it because Windows sure won't—that is, not unless you set SS_NOTIFY. I figure it's too much asking programmers to check some obscure style option in the control properties dialog—especially when your mind is racing with the excitement of adding this cool new feature to your About dialog—so I made CStaticLink extra programmer-friendly by turning the style bit on for you in case you forget. You're welcome.
Once SS_NOTIFY is set, the control receives WM_LBUTTONDOWN messages. You could handle them directly, but the official way to handle a mouse click in a static control is to handle the new-for-Win32
® STN_CLICKED notification. In typical Windows fashion, static controls send this notification to the parent window—not the control—but CStaticLink again uses MFC message reflection to handle its own STN_CLICKED. When the user clicks the control, CStaticLink::OnClicked calls the magic ShellExecute function to open the URL and send the user to Webland. Amazing.


Have a question about programming in C or C++? Send it to askpd@pobox.com

From the December 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1997 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2016 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft