Title: Use a tristate CheckBox to show partial selections in C#
The example Use a tristate CheckBox in C# explains how to let the user click a tristate CheckBox. This example demonstrates a particularly common usage: allowing the user to select some, all, or none of a set of other options.
In this example:
- If you check the Meals CheckBox, the program checks the Breakfast, Lunch, and Dinner CheckBoxes.
- If you uncheck the Meals CheckBox, the program unchecks the Breakfast, Lunch, and Dinner CheckBoxes.
- If you check or uncheck the Breakfast, Lunch, or Dinner CheckBoxes, the program makes the Meals CheckBox checked, unchecked, or indeterminate appropriately.
The most confusing part of this program is the way the CheckBoxes interact with each other. For example, suppose the CheckBoxes simply check and uncheck each other as necessary and suppose you check the Breakfast CheckBox. In that case the code must then update the Meals CheckBox. If the Lunch and Dinner choices are not checked, the program makes the Meals CheckBox indeterminate and its CheckStateChanged event handler fires. The program must then update the states of the Breakfast, Lunch, and Dinner CheckBoxes, so their event handlers fire. The process continues in a fairly confusing manner until eventually all of the CheckBoxes' states are set to unchecked when they are already unchecked. At that point, the states do not change so the event handlers don't fire. The program doesn't get stuck in a loop but the CheckBoxes are always unchecked. (Try commenting out the IgnoreCheckChangeEvents code described shortly and use the debugger to see what happens.)
To solve this problem, the program uses a boolean variable named IgnoreCheckChangeEvents. The event handlers check this variable and don't do anything when it is true.
The following code shows how the example responds when the user clicks the Meals CheckBox.
// True if we should ignore check change events.
private bool IgnoreCheckChangeEvents = false;
// Select or deselect all CheckBoxes.
private void chkMeals_CheckStateChanged(object sender, EventArgs e)
{
if (IgnoreCheckChangeEvents) return;
if (chkMeals.CheckState == CheckState.Indeterminate)
chkMeals.CheckState = CheckState.Unchecked;
CheckBox[] meal_boxes = { chkBreakfast, chkLunch, chkDinner };
IgnoreCheckChangeEvents = true;
foreach (CheckBox chk in meal_boxes)
chk.Checked = chkMeals.Checked;
IgnoreCheckChangeEvents = false;
}
The event handler first checks IgnoreChangeEvents and, if it is true, returns. That prevents the program from entering an infinite loop.
Next if the Meals CheckBox has the indeterminate state, the code changes it to Unchecked. This prevents the user from selecting the unchecked state by directly clicking on the Meals CheckBox.
The code then loops through the Breakfast, Lunch, and Dinner CheckBoxes making their Checked states match that of the Meals CheckBox. To prevent those controls' event handlers from reacting, the code sets IgnoreCheckChangeEvents to true before changing the Checked states and sets it to false after it is done.
The following code shows how the program reacts when the user clicks the Breakfast, Lunch, or Dinner CheckBoxes.
// The user changed a meal type selection.
// Update the chkMeals CheckBox.
private void chkMealType_CheckedChanged(object sender, EventArgs e)
{
if (IgnoreCheckChangeEvents) return;
// See how many meals are selected.
int num_selected = 0;
CheckBox[] meal_boxes = { chkBreakfast, chkLunch, chkDinner };
foreach (CheckBox chk in meal_boxes)
if (chk.Checked) num_selected++;
// Set the chkMeals CheckBox appropriately.
IgnoreCheckChangeEvents = true;
if (num_selected == 3)
chkMeals.CheckState = CheckState.Checked;
else if (num_selected == 0)
chkMeals.CheckState = CheckState.Unchecked;
else
chkMeals.CheckState = CheckState.Indeterminate;
IgnoreCheckChangeEvents = false;
}
If the program should ignore check change events, the code returns.
Next the code loops through the meal CheckBoxes, counting those that are selected. If all of the meals are selected, the program sets the Meals CheckBox state to Checked. If none of the meals are selected, the program sets the Meals CheckBox state to Unchecked. Finally if some meals are selected and others are not, the code sets the Meals CheckBox state to Indeterminate. The code uses the IgnoreCheckChangeEvents variable to prevent the Breakfast, Lunch, and Dinner CheckBoxes from reacting when the code changes their states.
The code may be a bit confusing but the result is intuitive for the user. It makes it easy for the user to select all, none, or some of the options.
Download the example to experiment with it and to see additional details.
|