The solution
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.