Dialog Box Control Management

The code

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()
    {
     updateControls();
    }
 
void CMyDialog::updateControls()
    {
     BOOL enable;
 
     // c_OK =========================================
     CString s;
     c_Text.GetWindowText(s);
     s.TrimLeft(); // ignore leading spaces
     enable = s.GetLength() != 0 &&
                 (c_Option.GetCheck == BST_UNCHECKED ||
                  c_Count != 0);
     c_OK.EnableWindow(enable);
 
     // c_Count =======================================
     enable = c_Option.GetCheck();
     c_Count.EnableWindow(enable);
     x_Count.EnableWindow(enable);
     //================================================
    }

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_Something.EnableWindow(mode);
     c_OtherThing.EnableWindow(!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;
     c_StopRun.GetWindowText(oldcaption);
     if(oldcaption != caption)
        c_StopRun.SetWindowText(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;
     CButton::GetWindowText(old);
     if(old == caption)
       return;
     CButton::SetWindowText(caption);
    }

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.

“You can stand on the shoulders of giants OR a big enough pile of dwarfs, works either way.”