Click here to Skip to main content
12,999,760 members (43,434 online)
Click here to Skip to main content
Add your own
alternative version


46 bookmarked
Posted 16 May 2000

Dialog Box Control Management

, 16 May 2000
Rate this:
Please Sign up or sign in to vote.
Learn how to effectively control your dialogs.


Dialog box control management is a perpetual problem. You want to have certain controls enabled or disabled, visible or invisible, based on the values of other controls. Unlike windows, there is no ON_UPDATE_COMMAND_UI mechanism for doing this. Most books that show examples of how to do it get it wrong. That's because most of them illustrate fairly simple examples, where the simple methods are easy to write and easy to maintain. Unfortunately, in the real world, dialog boxes are usually fairly complex, and the enabling conditions are nontrivial. The simple methods illustrated in the introductory textbooks are considerably less than adequate in this case. A decade of Windows programming has allowed me to develop a methodology for handling this.

Paul DiLascia has published a technique in his C++ Q&A column that suggests a technique for causing ON_UPDATE_COMMAND_UI messages to be handled in a dialog. Unfortunately, I've tried this a couple times and could not get it to work. It appears on the MSDN CD-ROM; find it by searching for WM_KICKIDLE and looking at the C++ Q&A columns that come up. However, the only difference between the technique I'm about to describe and the technique he describes is how the code is partitioned. In his solution, each control has an ON_UPDATE_COMMAND_UI handler, and the code to enable the control goes in that handler. In my solution, all the code is in one function. But the important feature of both these techniques is that the control manipulation code exists in precisely one place in the program.

The usual examples given in introductory textbooks (with one exception I am certain of: see Win32 Programming, by Brent Rector and Joseph M. Newcomer) show dialogs as enabling controls in response to button presses, listbox selections, and the like, in the code that responds to the event. This is the wrong place to do it.

Why is this the wrong place? Well, a good example is a piece of code I inherited (I'm a freelance consultant). It had twenty-three separate places where it enabled one control. As usual, this led to the case where each change (such as adding a new control) created a new condition for enabling the control in question, and not every site got updated consistently. In fact, I found five separate algorithms for determining enabling. And the order in which they were executed produced very odd results. In addition, at least two other places where the state changed so the enabling condition changed did not bother to do the computations at all. Maintaining code like this is a nightmare. Getting it right is impossible.

Of course, you might think "The obviously correct thing to do would have been to make a subroutine that did the enabling and call it from the right places". This assumes that you know the right places. While my solution is not perfect in this regard, it is a whole lot better than the normal distributed update mechanism.

What I do is treat the enabling condition as a set of constraint equations. Each control has at most one instance of "EnableWindow" and at most one instance of "ShowWindow". Period.

Every dialog of mine that has any interesting things happening to controls has a method called "updateControls". All state changes on all controls are computed in this method. The defect in this scheme is that I have to call updateControls on all state changes, which often means that I have control handlers (for example, for checkboxes) that do nothing but call updateControls. An alternative is to simply call the method from the OnIdle handler, which I choose not to do.

Furthermore, when the state of one control depends on one or more other controls, the state of the controls affecting it are directly accessed at the time the computation is done. No Boolean variables set magically from some other function. Every variable that can affect the state is computed when needed, and not an instant before. Don't do something clever like precompute the state based on some control state combination and store this in a Boolean for use by updateControls. Always compute from first principles, every time (well, if a database access is required, you can cache the database state, but in this case, the conditions should be computed directly from the fields of the record(s), and not from precomputed information that has been stored).

A potential objection to this method is that the distributed method computes only the state of the controls affected by the state change being processed, while my method requires recomputing the state of all the controls. This is "inefficient". This argument is fundamentally meaningless. It is based on early training of programmers that emphasizes that executing the fewest number of instructions possible to achieve a goal is the metric for a good program. This is the wrong training. If you believe this, rethink the problem. Efficiency matters only when it matters. The rest of the time, simplicity, correctness, and maintainability dominate. Efficient code is almost always harder to write, harder to debug, and harder to maintain than simple code.

Unless you are doing a computation-intensive algorithm, efficiency is a third- or fourth-order effect. Remember that many of these rules were developed in the era when machines were very slow. 30 MIPS? That's a midrange Pentium. The first machine I programmed executed at approximately 0.003 MIPS. That's a factor of 10,000 improvement in performance in 36 years. Back then, every instruction counted. Today, the only criterion is responsiveness, and the cost of developing a program (the largest program that machine could hold was about 1800 assembly-code instructions; compare that with a medium-sized Windows app which may run 60,000 to 120,000 lines of C code).

Why doesn't efficiency matter when you're updating controls? Look at the human factors. A mouse is held approximately 2 feet from the ear. Sound travels at approximately 1100 ft/sec. This means that it takes approximately 2ms for the sound of the mouse click to reach the ear. The neural path from the fingertip to the brain of an adult is approximately 3 feet. Propagation of nerve impulses is approximately 300 ft/sec, meaning the sensation of the mouse click takes approximately 10ms to reach the brain. Perceptual delay in the brain can add between 50 and 250ms more.

Now, how many Pentium instructions can you execute in 2ms, 10ms, or 100ms? In 2ms, on a 500MHz machine that's 1,000,000 clock cycles, so you can execute a lot of instructions in that time. Even on a now-clunky 120MHz Pentium there is no noticeable delay in handling the controls.

Therefore, the key idea is to make life as simple as possible, both for coding and for maintenance. Here's a sample that handles the updating of the OK button based on the emptiness of the c_Text control.

Here's the formal spec:

  • OK is disabled if c_Text is empty
  • OK is disabled if c_Option is selected and c_Count is 0
  • c_Count is enabled only if c_Option is checked
void CMyDialog::OnChangeText()

void CMyDialog::updateControls()
    BOOL enable;

    // c_OK =========================================
    CString s;
    s.TrimLeft(); // ignore leading spaces
    enable = s.GetLength() != 0 && (c_Option.GetCheck == BST_UNCHECKED 
                                                      || c_Count != 0);

    // c_Count =======================================
    enable = c_Option.GetCheck();

Sometimes, complex Boolean expressions get hard to write and debug. Another alternative is to rewrite the enable test for OK as:

enable = s.GetLength() != 0;
if(c_Option.GetCheck == BST_CHECKED && c_Count == 0)
    enable = FALSE;

The technique is to establish the value of 'enable', and then all subsequent modifications to the variable use either '&=' or simply set the value FALSE. The monotonicity of the changes must always favor moving towards FALSE. Once the value is set, at no point is any computation done that can explicitly set it TRUE. This discipline ensures that you won't accidentally set it TRUE after some other condition has set it (correctly) FALSE. You can set it explicitly to FALSE, or use &=, but never do anything that might possibly increase it from FALSE to TRUE. A similar rule is used to compute visibility. I tend to use a Boolean variable for visibility, applying the same technique of monotonically decreasing computation, then execute a statement of the form

c_Control.ShowWindow(visible ? SW_SHOW : SW_HIDE);

Often you want to enable/disable a group of controls based on a condition. I tend to call a function for such a group by first computing the desired state, and then calling a method such as shown below. The code implements controls that are complementary, and all based on the same condition:

  • If the condition is TRUE, c_Something is enabled.
  • If c_Something is enabled, c_OtherThing is disabled, and vice-versa.
  • If c_Something is enabled, c_ThisThing is visible, else it is invisible.
  • If c_OtherThing is enabled, c_ThatThing is visible, else it is invisible.
void CMyDialog::changeGroup(BOOL mode)
    c_ThisThing.ShowWindow(mode ? SW_SHOW : SW_HIDE);
    c_ThatThing.ShowWindow(mode ? SW_HIDE : SW_SHOW);

So although not all the code is necessarily inline in updateControls, the changeGroup method is not called from any place other than updateControls.

No, this isn't perfect. Perfect would be that the ClassWizard provided ON_UPDATE_COMMAND_UI handlers for every control and Microsoft did the work necessary to see they were called. The persistent defect in my method is that you must remember to call updateControls whenever the state changes. The good news is that if you call it at the end of each control handler, and nothing changes, nothing needs to be done. Note that you will typically call updateControls at the end of OnInitDialog.

Yes, there is one additional problem. If what is being computed is the caption for a control, you'll find this technique produces an undesirable flicker. This is easy to fix. Consider the following, which changes a button caption from "Stop" to "Run":

BOOL isrunning;  // protected class member; set FALSE in constructor

void CMyDialog::OnStopRun( )
    isrunning = !isrunning;
    updateControls( );

void CMyDialog::UpdateControls()
    CString caption;
    caption.LoadString(isrunning ? IDS_STOP : IDS_RUN);
    CString oldcaption;
    if(oldcaption != caption)

It should be obvious that if you have more than one button you are doing this to, there is a high payoff in defining a new CDCButton class (Dynamic Caption Button) which overrides SetWindowText:

void CDCButton::SetWindowText(CString caption)
    CString old;
    if(old == caption)

When Controls Aren't There Yet: ASSERT failures

There is a problem during dialog startup. For example, if you have an OnChange or OnUpdate handler for an edit control, and you wish to manipulate another control. Even if you don't believe my methodology about always doing this in a single routine, you will still have serious problems. Consider the case where you want to enable a button when the text becomes nonempty. The test looks like

CString s;
c_DoSomething.EnableWindow(s.GetLength() > 0);

You find yourself in the middle of an ASSERT statement which, if you trace back, came from the EnableWindow call. You look at the m_hWnd member of the c_DoSomething button, you find that it is 0. If you are using GetDlgItem, it is worse, because you would have written something like

CButton * doSomething = (CButton *)GetDlgItem(IDC_DO_SOMETHING);

and your attempt to use it would take an access fault because the pointer was NULL. (Don't use GetDlgItem; see my essay on the right way to do this).

This happens because of a mismatch between MFC and the underlying Windows mechanisms. What has happened is that the DDX mechanism has created the edit control, which means that it can start generating messages that the MESSAGE_MAP will start dispatching, but has not yet assigned the IDC_DO_SOMETHING control to its corresponding CButton. Hence the ASSERT failure when you try to use it, even though the control already exists.

The most common cause of the GetDlgItem failure is that in the tab order the sequence is to create the edit control, create its spin-control buddy, which then generates a message to the dialog, which intercepts it, but the dialog creation code has not yet created the IDC_DO_SOMETHING button. So GetDlgItem necessarily returns a NULL pointer.

The way I get around this is to create a member variable of my dialog class,

BOOL initialized;

In the class constructor, I simply set

initialized = FALSE;

At the end of OnInitDialog, I do the following:

initialized = TRUE;

and I modify updateControls() to have, as its first executable code, the statement


There are occasional other places I need to perform this test. Often you find these empirically.

You can sometimes replace the test with the implicit test,


for example, which tests the control variable c_Button to see if the window has been created. Since I usually have to introduce the initialized variable, I find this variant less common in my code.


Of the variety of techniques for control management in dialogs, a decade of Windows programming has convinced me that the core philosophy of this method is the only philosophy that can ever make sense. Whether it is done by explicit call, as I do, or from OnIdle, or distributed to ON_UPDATE_COMMAND_UI handlers, there must be no more than one ShowWindow and one EnableWindow per control. To do otherwise is simply insane. It produces code that is difficult to write, difficult-to-impossible to debug, and utterly impossible to maintain. That is unacceptable.

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 with questions or comments about this web site.
Copyright © 1999 <!--webbot bot="Substitution" s-variable="CompanyLongName" startspan -->CompanyLongName<!--webbot bot="Substitution" endspan i-checksum="22147" --> All Rights Reserved.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Joseph M. Newcomer
United States United States
PhD, Computer Science, Carnegie Mellon University, 1975
Certificate in Forensic Science and the Law, Duquesne University, 2008

Co-Author, [i]Win32 Programming[/i]

You may also be interested in...


Comments and Discussions

QuestionWhere to put calls to updateData()? Pin
Supermagle12-Jan-06 4:36
memberSupermagle12-Jan-06 4:36 
AnswerRe: Where to put calls to updateData()? Pin
Joseph M. Newcomer12-Jan-06 6:26
memberJoseph M. Newcomer12-Jan-06 6:26 
GeneralRe: Where to put calls to updateData()? Pin
Supermagle12-Jan-06 21:07
memberSupermagle12-Jan-06 21:07 
GeneralRe: Where to put calls to updateData()? Pin
Joseph M. Newcomer13-Jan-06 4:37
memberJoseph M. Newcomer13-Jan-06 4:37 
GeneralCommand update handlers can be made to work Pin
Nick Gammon28-Jun-00 1:10
sussNick Gammon28-Jun-00 1:10 
I want to comment on your technique of updating dialog controls in response to user input. The method described is ingenious, but a rather simpler method exists using the WM_KICKIDLE function described by Paul DiLascia. I have used this in many, many places without any problems. The only minor problem is if the pages are property pages, in which case you need to add an extra step to the property sheet, as described below.

The benefits of this method are:

a) Minimal extra code needed
b) The decisions about whether to enable a control are made *per control*.
c) You don't need to worry about testing for whether the controls have been created yet.
d) The controls don't flicker
e) By glancing at the message map in the .cpp file for the dialog you can instantly see which controls you have provided "idle update" processing for.
f) You don't need to remember to call "updateControls" for every control, that changes, that might affect the decision to update some other control.

My example below assumes you have a "find" dialog box for which you want to disable the "Find" button if the user has not entered any text to search for.

1. Add this to stdafx.h, to give the definition for WM_KICKIDLE:

#include <afxpriv.h>

2. In the .h file for the dialog, add a handler for KickIdle, plus one for each control that needs updating:

// Generated message map functions
afx_msg LRESULT OnKickIdle(WPARAM, LPARAM); // add this once
afx_msg void OnUpdateFindButton(CCmdUI* pCmdUI); // one per control

3. In the .cpp file for the dialog, add message maps for WM_KICKIDLE and an ON_UPDATE_COMMAND_UI handler for each control that needs updating:

ON_MESSAGE(WM_KICKIDLE, OnKickIdle) // add this once
ON_UPDATE_COMMAND_UI(IDC_FIND_BUTTON, OnUpdateFindButton) // one per control

4. Add the implementation for OnKickIdle, and the ON_UPDATE_COMMAND_UI handler for each control:

UpdateDialogControls (AfxGetApp()->m_pMainWnd, false);
return 0;
} // end of CMyExampleDlg::OnKickIdle

void CMyExampleDlg::OnUpdateFindButton(CCmdUI* pCmdUI)
CString strText;
m_ctlFindText.GetWindowText (strText);
pCmdUI->Enable(!strText.IsEmpty ());
} // end of CMyExampleDlg::OnUpdateFindButton

5. If the dialogs are really property pages, you must forward the WM_KICKIDLE from the property *sheet* that the pages belong to. To do this you must have derived your own property sheet based on CPropertySheet (the property sheet component does this automatically), and add an OnKickIdle handler (in the same way as described above), but in the processing for OnKickIdle you forward the WM_KICKIDLE message to the active page:

LRESULT CMyPropertySheet::OnKickIdle(WPARAM, LPARAM)
GetActivePage ()->SendMessage (WM_KICKIDLE, 0, 0); // pass message on
return 0;
} // end of CMyPropertySheet::OnKickIdle
GeneralRe: IDOK and CPropertySheet Pin
Daniel5-Nov-00 4:57
memberDaniel5-Nov-00 4:57 
GeneralRe: IDOK and CPropertySheet Pin
Nick Gammon5-Nov-00 10:13
memberNick Gammon5-Nov-00 10:13 
GeneralGreat Idea - I've used it for years! Pin
Alvaro Mendez18-May-00 7:02
sussAlvaro Mendez18-May-00 7:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170624.1 | Last Updated 17 May 2000
Article Copyright 2000 by Joseph M. Newcomer
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid