Avoiding GetDlgItem in MFC

Creating Control Variables

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 CNumericEdit from 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

        CNumericEdit c_Count;

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)
  {
   CDialog::DoDataExchange(pDX);
   //{{AFX_DATA_MAP(CMyClass)
   DDX_Control(pDX, IDC_COUNT, c_Count);
   //}}AFX_DATA_MAP
  }

Like most //{{ //}} 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 GetDlgItem

After your 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) ...

because GetWindowInt is a method of my CNumericEdit class. I can even write

c_Count.SetWindowText(37);

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.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” - Martin Fowler