When you learned to program Windows in C, you learned about
GetDlgItem. This is the way to get a handle for a control when you are working with a dialog. In C, it is the only way to get a window handle for the control. In C++/MFC, there is a better way. It makes about as much sense to use
GetDlgItem in C++/MFC as it does to program Windows in assembly code. Yes, I know there are masochists who like to program Windows in assembly code. But it is rare that it is actually needed. Perhaps in the inner loop of a DSP algorithm, where you might drop a few of those fancy Pentium III opcodes inline, but the rest of the time it is insanity. The same is true of
GetDlgItem. Actually, there is slightly more reason to use a raw
GetDlgItem than there is to use assembly code, but the reasons represent rare and very uncommon cases.
My view is, if you are writing more than one
GetDlgItem per year, you are probably not using C++/MFC correctly. Fortunately, there is a better, more elegant, and safer way to get access to controls using MFC.
Note that you should almost never use
UpdateData in a dialog. If you use it, you should use it only in a modeless dialog. There is, as far as I can tell, absolutely no excuse for using
UpdateData in a modal dialog. I discuss this in much more detail in another essay. But the techniques shown here are fundamental to avoiding the use of
UpdateData as well. A simple rule: If you're calling
UpdateData in a modal dialog, you're not using MFC correctly.
To avoid the use of
GetDlgItem, you must create a control variable to represent your control. You can create a control variable for any control that has an ID other than
IDC_STATIC. Due to what appears to be terminal insanity, you have to go through an unfortunate set of complex machinations, discussed below, to create control variables for radio buttons other than the first in a group; I cannot figure out why this is viewed as a good idea, but Microsoft seems to have a peculiar idea about how you should use radio buttons.
To create a control variable, you activate ClassWizard, select the Member Variables tab, highlight the control you want a variable for in the list box, and click the Add Variable button. You will get a window much like the one shown below (which has already been filled in):
In the "Member variable name" window you type the name of your member variable. The window starts out preloaded with "
m_". I found immediately that this was confusing. For some controls, you can have both a control variable and a value variable. I use "
m_" for the value variable (a feature I rarely use, as it turns out), and use "
c_" as a prefix for control variables. Trust me on this one: if you start using
m_ to represent control variables, you will quickly come to regret it. Been there, done that. That's why I have a new convention. It works. For other conventions, see the discussion below.
Go to the
Category window. For some controls, you can only have a
Control variable, and that is the only option. For others, such as an edit control, you can have a
Control or a
Value variable. The default is
Value, so you have to do what I have done in the illustration and select
Control as the category. Having selected a
Control variable, you may now select the type. If you have a class you have derived directly from a base class, as I have derived
CEdit, ClassWizard will recognize it and present it as one of the options in the Variable Type window. Since
c_Count is an edit control representing a count, I selected my
CNumericEdit class as the variable type.
Unfortunately, if you have a class which is derived from one of your classes that is derived from a base class, ClassWizard can't cope (ClassWizard can't cope with a lot of common things you would like to do...). In this case, just select the base type, and you'll have to hand-edit it later.
What this gives you is a variable of the selected type. If you look in your
.h file, you will find a declaration has been added
It is your responsibility to see that the appropriate header file (
NumericEdit.h, in this case) is included before the dialog header file so the user-defined control types are known.
If you have (as I usually do), two or more derivation levels to get to your useful control (for example, I have a fancy ComboBox class,
CSmartCombo, and one which displays little icons with the selections,
CImageCombo, which is derived from
CSmartCombo, which is derived from
CComboBox, you can't specify this class. If I want a
CImageCombo control, I just tell it
CComboBox is the variable type, and then I go in and change the header file by hand. Silly, but ClassWizard is not a wizard you want to leave to carry water if there are brooms nearby...
Having done this, you will notice another feature: in the
.cpp file there has been a function all along, which now has the line:
void CMyClass::DoDataExchange(CDataExchange * pDX)
DDX_Control(pDX, IDC_COUNT, c_Count);
style ClassWizard comments, you shouldn't mess with the contents of the delimited block yourself unless you are willing to risk the consequences. What the DDX_Control line does is map the
HWND of the control to the variable, in this case
IDC_COUNT is mapped to
c_Count. And yes, it does it under the floor with
OnInitDialog handler calls
CDialog::OnInitDialog, these variables are available for use. Thus, instead of writing
CButton * button = (CButton *)GetDlgItem(IDC_BUTTON);
if(button->GetCheck( ) == BST_CHECKED) ...
you can write
if(c_Button.GetCheck( ) == BST_CHECKED) ...
In my case, I can write
if(c_Count.GetWindowInt() == 0) ...
GetWindowInt is a method of my
CNumericEdit class. I can even write
because I've overloaded
SetWindowText in my class to take integer values.
And that represents one of the problems of having those hundreds of
GetDlgItem casts: what if you subclass a control? You have to find all uses of the control in
GetDlgItem and change the casts. Say, for example, you've overridden
AddString in your
CListBox-derived class to recompute the horizontal extent and call
SetHorizontalExtent, and consequently also overridden
ResetContent to set the horizontal extent to 0. If you then change some ordinary
CListBox variable to be your new
CHorzListBox class, you have to find all the casts to
CListBox, and change only those which apply to your new
CHorzListBox class. Ugly, isn't it? Whereas if you use control variables, all you do is change the variable type in the declaration, and all the overloading and inheritance work correctly. Much better. This is how C++ is supposed to be used.
This is not without its problems. Some of the problems were there already when you used
GetDlgItem, and some are artifacts of a limited world view of ClassWizard.
We've already mentioned that ClassWizard can't cope with more than one derivation level. Silly, but I've been complaining about this since ClassWizard was introduced in 16-bit MFC and nobody pays attention.
Another intrinsic problem, which represents some sort of strange philosophical viewpoint of Microsoft, is that you must not be allowed to create control variables for radio buttons. This makes no sense. They have some weird idea that the only way you will ever manipulate radio buttons is via an index. This is hopelessly inadequate. Therefore, you have to go through some serious contortions to get control variables for your radio buttons.
The first thing you have to do is go back and mark all radio buttons as having the
WS_GROUP style. Only radio buttons with a
WS_GROUP style can have a control variable. However, if you mark all of them with
WS_GROUP, create the control variables, and then remove the
WS_GROUP attribute, everything works just fine, thank you. Why we have to go through these extra steps makes no sense whatsoever, but like the derived classes problem, I've been complaining about this for years with no effect. My problem is that I keep forgetting to go back and undo all the
WS_GROUP attributes, so the first time I run the program after this I find that all my radio buttons are one-button groups. Whoops. $#%! Fix, and recompile/relink.
This problem actually already exists, and you may have been hit by it. But for those of you who haven't been, here's what can happen to you.
During dialog creation or the
DDX_Control initialization, controls can generate messages. These messages often have handlers that want to assume the controls are present. They may not be. You have no real control over the order of
DDX_Control initialization. Consider two simple examples:
Example 1: Resizing controls
You can create a dialog with a resize border. Using this resize border you can drag the dialog edges around to resize it. A typical use might be if you have a list box in the dialog, most conveniently placed at the bottom of the dialog. As you stretch the dialog horizontally or vertically, you want to stretch the list box so it fits the entire dialog. Looks simple:
void CMyDialog::OnSize(UINT nType, int cx, int cy)
CDialog::OnSize(nType, cx, cy);
c_ListBox.SetWindowPos(NULL, 0, 0, r.Width(), r.Height() - lb.top,
SWP_NOMOVE | SWP_NOZORDER);
OnSize routine can be called very early;
WM_SIZE is one of the first five messages sent when a window is created. This means that at the time the dialog is created, the
OnSize handler is called, but none of the controls have been created yet! The result is the
c_ListBox.GetWindowRect takes an access fault somewhere deep in Windows. Ugly.
Example 2: A spin control with an autobuddy.
If you have a
CSpinCtrl which has its "Autobuddy" attribute set, this means that it will set the value of its buddy control (such as an edit control) whenever its value changes. It also means that it will set that control to 0 when the spin control is created. This generates a
WM_UPDATE sequence to the edit control. If you are looking for this event, and want to change the state of some other control based on the change, your attempt to access the other control will cause an access fault if that other control is not already defined.
Both of these problems can be solved by adding an extra variable to the dialog class. Add a protected variable, "
BOOL initialized". In the dialog class constructor, set "initialized" to
FALSE. (Note that I do not use the
m_ prefix for class variables that are not public and set or read by the
DoModal caller. I've been programming far too many years to think this convention is useful. Also, you will never see me use Hungarian notation in any variable I define, anywhere, at any time. But that's another discussion). You must set this in the constructor, not in
OnInitDialog, because by the time you get to the assignment in
OnInitDialog it is far too late. To protect against the possibility of accessing an invalid variable, you just check the
initialized variable. For example,
void CMyDialog::OnSize(UINT nType, UINT cx, UINT cy)
CDialog::OnSize(nType, cx, cy);
Microsoft has some weird ideas about naming conventions. Hungarian notation was invented in the days before C had prototypes and thus cross-module checking. It probably had some utility in the days before C resembled a real programming language. It has absolutely no value today, and should be avoided. I find the
m_ convention for member variables equally insane. I use it only in limited situations, in particular, only for values associated with controls. I only need value variables for controls whose values are passed into the dialog from outside, or which are passed back to the caller. Since I rarely do this, the occurrence of
m_ variables in my code is very, very low.
There are other conventions I use. For example, I prefix the control variables with "
c_". This indicates that it is a control variable. It is necessary to use a different convention than "
m_", because some controls can have both a control variable and a value variable. In addition, it is often useful to be able to name the caption associated with some controls, such as an edit control. This is useful if you have to enable/disable the controls. Consider the case of an edit control,
c_Text, which has a static control which is the caption. I use the prefix "
x_" to indicate the caption. This introduces much sanity into the world. If I want to preload the text control by passing the value in, I create a value variable,
m_Text. To access the control, I create a control variable,
c_Text. To access its caption, I create a control
x_Text. I never access the
m_ variable within the dialog, unless I've provided a "reset to initial state" button which resets the control to its initial value. There is no need to access the value variable. If accessed, I only read it.
This means that the DDX calls will look something like
DDX_Control(pDX, IDC_TEXT, c_Text);
DDX_Text(pDX, IDC_TEXT, m_Text);
DDX_Control(pDX, IDC_TEXT_CAPTION, x_Text);
To enable or disable the text control and its corresponding caption you can then write something like
void CMyDialog::updateControls( )
enable = ...
Note how clear this is. Easy to write, easy to understand, easy to maintain. What's that
updateControls method? That's another essay!
The GetDlgItem method has limited usefulness in writing MFC code. If you write it, there is an excellent chance you are simply not using MFC correctly. Certainly there are exceptions, and I show one in my essay on dialog control managment. But such instances are rare. A well-written MFC application will have no gratuitous instances of
GetDlgItem, anywhere, and no instances of
UpdateData in any modal dialog, ever.
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.
Send mail to email@example.com with questions or comments about this article.
Copyright © 1999 <!--webbot bot="Substitution" s-variable="CompanyLongName" startspan -->CompanyLongName<!--webbot bot="Substitution" endspan i-checksum="22147" --> All Rights Reserved.