Avoiding GetDlgItem in MFC

Pitfalls & Gotchas

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. 

Radio button variables

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. 

Uninitialized Controls

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);
    CRect r;
    GetClientRect(&r);
    CRect lb;
    c_ListBox.GetWindowRect(&lb);
    ScreenToClient(&lb);
    c_ListBox.SetWindowPos(NULL, 0, 0, r.Width(), r.Height() - lb.top,
                           SWP_NOMOVE | SWP_NOZORDER); 
   }

Unfortunately, the 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_CHANGE and 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);
    if(!initialized)
       return;
    // ... whatever else you want to do}

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.

“The most exciting phrase to hear in science, the one that heralds new discoveries, is not 'Eureka!' but 'That's funny...'” - Isaac Asimov