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 "
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
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
enable = s.GetLength() != 0 && (c_Option.GetCheck == BST_UNCHECKED
|| c_Count != 0);
enable = c_Option.GetCheck();
Sometimes, complex Boolean expressions get hard to write and debug. Another alternative is to rewrite the enable test for
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
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
c_Something is enabled.
c_Something is enabled,
c_OtherThing is disabled, and vice-versa.
c_Something is enabled,
c_ThisThing is visible, else it is invisible.
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
changeGroup method is not called from any place other than
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
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 "
void CMyDialog::OnStopRun( )
isrunning = !isrunning;
caption.LoadString(isrunning ? IDS_STOP : IDS_RUN);
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
void CDCButton::SetWindowText(CString caption)
if(old == caption)
When Controls Aren't There Yet: ASSERT failures
There is a problem during dialog startup. For example, if you have an
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
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
The way I get around this is to create a member variable of my dialog class,
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 email@example.com 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.