C++ Q&A;: Ctrl Keys in MFC, Function Overloading, Checking for Null

MSDN Magazine

Ctrl Keys in MFC, Function Overloading, Checking for Null
Download the code for this article:C0205.exe (36KB)

Q How do I get the edit control keys Strg+C, Strg+X and Strg+V to work in my MFC application? I want to use your old FormSwap application (see the September 1998 and December 1998 issues of Microsoft® Systems Journal). I realized that the edit controls don't execute the shortcuts (Strg+X,V,C) although in a normal dialog-based application they do without any effort.

Walter Reiser


A
First of all, since Walter hails from Austria, let me clarify that Strg is the standard computer abbreviation for the German word Steuerung, which means control; in English we say Ctrl-C, Ctrl-X, and so on.
      Well, now that you've suffered through your international awareness lesson, let me turn to Walter's question. The problem here is very simple. In my original FormSwap program, the main resource (.rc) file has the following entries:

  IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE 
  
BEGIN
"C", ID_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT
"V", ID_VIEW_TEST, VIRTKEY, CONTROL, NOINVERT
"X", ID_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT
END

 

In other words, the main app's accelerator table is overriding the control keys for the edit controls. So all you have to do is remove the three entries above and presto, the problem is solved. This is a common bug that creeps in when you copy code from one app to another. It doesn't happen with AppWizard-generated dialog apps because Visual Studio® is smart enough to omit the edit keys from the main accelerator table in the case of a dialog-based app. But if you copy the resource file from a normal (non-dialog-based) app, you can introduce this bug.
      The answer also reveals, by the way, one way to override the edit keys if you want to make them do something else: just bind the keys to new commands in your app's accelerator table. Of course, this would be most bizarre and even sadistic to your users: Ctrl-C, X, and V should always be Copy, Cut, and Paste, no matter what. But you might want to trap these keys in order to extend, rather than change, their behavior.
      I said the accelerator is one way to override the edit keys, implying that there are others. Indeed, accelerators aren't really even the best way to override edit keys in an edit control. Why not? Because they override the keys at the application or global level. In most cases, a more sensible and object-oriented way is to override the keys in the edit control itself, because conceptually the special behavior is usually a property of the edit control, not the application. If you want to use the same specialized behavior in another app, should you really have to modify the app's accelerator table and reimplement special commands? Wouldn't it be easier and make more sense to simply (re)use your already modified CSpecialEdit, or what-ever you called it, in the second app?
      Overriding the edit keys in an edit control is easy. Edit controls process these keys as ordinary WM_CHAR messages, not as accelerators, so all you have to do in this case is override WM_CHAR/OnChar. But that's if you want to override the keystrokes only (which would be rather strange), as opposed to the commands they invoke (Copy, Cut, Paste), which can come from either keyboard or context menu. In order to override the commands, you have to handle the WM_CUT, WM_COPY, and WM_PASTE messages. Got it?

Q
I have a small class hierarchy with a virtual overloaded function I'm trying to override in a derived class. Figure 1 gives a simplified picture of what I'm trying to do. The base class has two overloaded test functions. When I override one of the overloads in the derived class, I thought the other one would be inherited, but when I compile, it bonks out. I get an error message:

  ... C2660 : 'test' :
  
function does not take 1 parameters

 

      So my question is, can I overload and override a function at the same time (without losing polymorphic behavior)?

Destin Szelong


A
Many a C++ programmer has puzzled this perplexity. To understand what's going on here, you have to realize there are two somewhat conflicting concepts that come into play. One is function overloading; the other, namespace scoping.
      From the perspective of overloading, you might think C++ should work the way you want; that is, if your derived class overrides only one of a pair of overloaded functions, then the other should be inherited the normal way. But from a namespace perspective, when C++ tries to resolve a symbol name, it searches its order of scopes from near to far; it looks first for a local variable, then a class member name, then a base class member, then a global, and so on. Once the compiler resolves a name, it stays resolved. Period. There's no notion that a symbol name can be resolved in two different scopes.
      So in your example, once the compiler determines that the name "test" appears in class D, that's where the name lives. This rule applies no matter which function call the compiler is trying to resolve, whether it's the call test with no arguments, or test(17) with one integer argument. In the latter case, once the compiler resolves the name "test" as living in the scope of D, it ceases its scope explorations, and looks for a function with an exact argument match. Since it finds no test function in D that takes an integer, it spits an error. Compiler jocks say the function D::test(void) hides the function B::test(int).
      You might well ask, "why does it have to be that way?" and indeed that's a good question. I can only refer you to the C++ Annotated Reference Manual by Bjarne Stroustrup, section 13.1, where Mr. C++ himself discusses the issue. Briefly, the reasoning is that searching deep into the class hierarchy for overloaded functions that match a signature is more likely to cause confusion than not searching.
      What can you do? How can you solve your problem? Easy. Just create another test function in the derived class that calls the base explicitly, like so:

  class D : public B {
  
public:
virtual void test(int x) { B::test(x); }
•••
};

 

Now D has both test functions and everything is fine in the world. It requires a little extra typing, but hey, life ain't always perfect.

Q
Is there ever a reason to check if AfxGetApp returns a valid pointer? Normally, I always check pointers for NULL before using them. However, most of the MFC source code and examples on MSDN® use the returned pointer outright without checking. I'm assuming this is because of the globally created variable theApp. Also, in situations where one needs to make multiple calls to AfxGetApp, it seems prudent to keep a local copy of the returned pointer and use it (especially since AfxGetApp() calls AfxGetModuleState()->m_pCurrentWinApp). For example:

  void CWindowPlacement::WriteProfileWP(LPCSTR lpKeyName)
  
{
CWinApp *pApp = AfxGetApp();
pApp->WriteProfileInt(...);
pApp->WriteProfileInt(...);
pApp->WriteProfileInt(...);
pApp->WriteProfileInt(...);
}

 

      Is it advisable to store the returned pointer in a class member? Or is that not a good idea because of the managed state business?

Mike Rizzi


A
Well, generally I'm a big fan of checking for NULL and I always advocate liberal use of ASSERT, but in this case I'd have to say that if AfxGetApp returns NULL, your code won't be the first place your app dies a miserable death. AfxGetApp is so ubiquitous throughout MFC that if it fails, the whole shebang will come quickly tumbling down, like Humpty Dumpty having a great fall. So I'd say it's safe to assume AfxGetApp returns a valid CWinApp. As for assigning to a local variable, there's certainly no harm there, nor is there any reason not to store the app pointer in a class member variable. For all practical purposes AfxGetApp returns a global pointer, whether it's literally a global as in the case of an EXE with static global theApp, or a DLL where the app pointer is stored indirectly as part of the DLL's MFC state. So it's okay to store a pointer, but why bother? If you're worried about performance, it's insignificant; you're talking a dram of a drop in a mote on a spec of sand in the ocean—unless you're calling AfxGetApp inside some tight loop that calculates 10,000-digit prime numbers or analyzes SETA satellite data. In my own apps, I often declare the application object extern:

  extern CMyApp theApp;
  
—so I can write code like
// set profile name
theApp.WriteProfileInt(...);
theApp.WriteProfileInt(...);
theApp.WriteProfileInt(...);
•••
// etc.

 

Corrections

      In the February 2002 issue of MSDN Magazine, at the beginning of my answer to the first question, I said, "A base class doesn't 'know' anything about what class it's derived from." Well, give me a conehead and call me dunce! Obviously, what I meant to say was, "A base class doesn't 'know' anything about which classes are derived from it." Fortunately, nobody seemed to notice. I guess either everyone is asleep, or you all understood what I meant (which was clear if you read the rest of the column). I apologize for any confusion I may have caused. To repeat: Classes always know which classes they're derived from, but never which classes are derived from them. Whew! (See the C++ Q&A Archive for links to all previous columns.)
      And while I'm on the subject of boo-boos, reader David Lowndes pointed out an interesting flaw in my March 2002 column. There Frank Lagattuta asked how to change his listbox's style to LBS_OWNERDRAWFIXED. I described how to change a control's window style using either PreCreateWindow (for a dynamically created control) or PreSubclassWindow (when subclassing an already existing one). The PreSubclassWindow technique did in fact work for Frank, but only because of a fluke situation. Unbeknownst to me and the readers, Frank was trying to change his listbox's style from LBS_OWNERDRAWVARIABLE to LBS_OWNERDRAWFIXED. If, however, you use the PreSubclassWindow method in an attempt to change the style from non-owner draw to owner-draw, your attempt will fail:

  void CMyListBox::PreSubclassWindow()
  
{
// Change to owner-draw—or does it?
// Fails if not already owner-draw!
ModifyStyle(0,LBS_OWNERDRAWFIXED);
}

 

      Why does it fail? Because Windows only looks for LBS_OWNERDRAWXXX when the control is first created. Once the control is created, you can ModifyStyle to LBS_OWNERDRAWFIXED all you want, Windows will ignore you. In general, some styles just can't be changed after a window has been created. Unfortunately, I don't have a definitive list of styles to give you; finding them is a trial-and-error process.
      So, how can you change the style of a listbox after it's been created? Well, assuming you can't simply edit the style in your dialog template (either in the .rc file or using the dialog editor in Visual Studio), the only option is to destroy the control and recreate it with the added style. Figure 2 shows a fragment of code from a program I wrote that does it. Most of it is pretty straightforward. You save the ID, position, style and extended style, before destroying the control, then use these saved values when you create the new one. If you need to save the list items and current selected item, don't forget to do that too! In general, you have to save the control's entire state, everything you care about. One tricky part is that in order to preserve the Tab order, you have to get the previous window (in Z-order) by calling GetWindow(GW_HWNDPREV), then call SetWindowPos to move the newly created listbox above it. Remember that for dialogs, the Tab order is the same as the Z-order. Happy Programming!

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 https://www.dilascia.com.

From the May 2002 issue of MSDN Magazine