Windows Forms
CheckBox
CheckedChanged
CheckStateChanged
.NET

Windows Forms' CheckBox CheckedChanged vs. CheckStateChanged

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

CheckedChanged fires when the Checked property (bool) changes between true and false. CheckStateChanged fires when the CheckState property (enum) changes between Unchecked, Checked, and Indeterminate. For standard two-state checkboxes, both events fire identically. The difference only matters when ThreeState = trueCheckStateChanged fires for all three transitions, while CheckedChanged only fires when the boolean Checked property actually changes (it does not fire when transitioning between Indeterminate and Checked since both have Checked = true).

The Two Properties

csharp
1// Checked property — boolean (two states)
2checkBox.Checked = true;   // Checked
3checkBox.Checked = false;  // Unchecked
4
5// CheckState property — enum (three states)
6checkBox.CheckState = CheckState.Unchecked;     // Empty box
7checkBox.CheckState = CheckState.Checked;        // Checkmark
8checkBox.CheckState = CheckState.Indeterminate;  // Filled/grayed box

The relationship between them:

CheckStateChecked
Uncheckedfalse
Checkedtrue
Indeterminatetrue

Both Checked and Indeterminate map to Checked = true. This is why CheckedChanged does not fire when transitioning between these two states.

Two-State CheckBox (Default)

csharp
1public partial class Form1 : Form
2{
3    public Form1()
4    {
5        InitializeComponent();
6
7        var checkBox = new CheckBox { Text = "Enable Feature", Location = new Point(10, 10) };
8
9        checkBox.CheckedChanged += (s, e) =>
10        {
11            Console.WriteLine($"CheckedChanged: Checked={checkBox.Checked}");
12        };
13
14        checkBox.CheckStateChanged += (s, e) =>
15        {
16            Console.WriteLine($"CheckStateChanged: State={checkBox.CheckState}");
17        };
18
19        Controls.Add(checkBox);
20    }
21}
22
23// User clicks:
24// CheckedChanged: Checked=True
25// CheckStateChanged: State=Checked
26
27// User clicks again:
28// CheckedChanged: Checked=False
29// CheckStateChanged: State=Unchecked

For two-state checkboxes, both events fire on every click with equivalent information.

Three-State CheckBox

csharp
1var checkBox = new CheckBox
2{
3    Text = "Three State",
4    ThreeState = true,
5    Location = new Point(10, 10)
6};
7
8checkBox.CheckedChanged += (s, e) =>
9{
10    Console.WriteLine($"  CheckedChanged: Checked={checkBox.Checked}");
11};
12
13checkBox.CheckStateChanged += (s, e) =>
14{
15    Console.WriteLine($"  CheckStateChanged: State={checkBox.CheckState}");
16};
17
18// User clicks through all three states:
19
20// Click 1: Unchecked → Checked
21//   CheckedChanged: Checked=True
22//   CheckStateChanged: State=Checked
23
24// Click 2: Checked → Indeterminate
25//   CheckStateChanged: State=Indeterminate
26//   (CheckedChanged does NOT fire — Checked is still true)
27
28// Click 3: Indeterminate → Unchecked
29//   CheckedChanged: Checked=False
30//   CheckStateChanged: State=Unchecked

The critical difference: transitioning from Checked to Indeterminate does not fire CheckedChanged because Checked remains true in both states.

Which Event to Use

Use CheckedChanged for Two-State Logic

csharp
1// Simple on/off toggle
2checkBox.CheckedChanged += (s, e) =>
3{
4    if (checkBox.Checked)
5    {
6        EnableFeature();
7        statusLabel.Text = "Feature enabled";
8    }
9    else
10    {
11        DisableFeature();
12        statusLabel.Text = "Feature disabled";
13    }
14};

Use CheckStateChanged for Three-State Logic

csharp
1// "Select All" checkbox with indeterminate state
2selectAllCheckBox.ThreeState = true;
3selectAllCheckBox.CheckStateChanged += (s, e) =>
4{
5    switch (selectAllCheckBox.CheckState)
6    {
7        case CheckState.Checked:
8            SelectAllItems();
9            break;
10        case CheckState.Unchecked:
11            DeselectAllItems();
12            break;
13        case CheckState.Indeterminate:
14            // Some items selected — do nothing (user must choose)
15            break;
16    }
17};

"Select All" Pattern

csharp
1public partial class Form1 : Form
2{
3    private CheckBox _selectAll;
4    private CheckBox[] _items;
5    private bool _updating;
6
7    public Form1()
8    {
9        InitializeComponent();
10        SetupCheckBoxes();
11    }
12
13    private void SetupCheckBoxes()
14    {
15        _selectAll = new CheckBox { Text = "Select All", ThreeState = true };
16        _items = new CheckBox[]
17        {
18            new CheckBox { Text = "Item 1" },
19            new CheckBox { Text = "Item 2" },
20            new CheckBox { Text = "Item 3" }
21        };
22
23        _selectAll.CheckStateChanged += SelectAll_CheckStateChanged;
24        foreach (var item in _items)
25        {
26            item.CheckedChanged += Item_CheckedChanged;
27        }
28    }
29
30    private void SelectAll_CheckStateChanged(object sender, EventArgs e)
31    {
32        if (_updating) return;
33        _updating = true;
34
35        bool check = _selectAll.CheckState == CheckState.Checked;
36        foreach (var item in _items)
37        {
38            item.Checked = check;
39        }
40
41        _updating = false;
42    }
43
44    private void Item_CheckedChanged(object sender, EventArgs e)
45    {
46        if (_updating) return;
47        _updating = true;
48
49        int checkedCount = _items.Count(cb => cb.Checked);
50        if (checkedCount == 0)
51            _selectAll.CheckState = CheckState.Unchecked;
52        else if (checkedCount == _items.Length)
53            _selectAll.CheckState = CheckState.Checked;
54        else
55            _selectAll.CheckState = CheckState.Indeterminate;
56
57        _updating = false;
58    }
59}

The _updating flag prevents infinite event loops when programmatically changing checkbox states.

Event Firing Order

csharp
1checkBox.CheckedChanged += (s, e) => Console.WriteLine("1. CheckedChanged");
2checkBox.CheckStateChanged += (s, e) => Console.WriteLine("2. CheckStateChanged");
3
4// When user clicks (Unchecked → Checked):
5// 1. CheckedChanged
6// 2. CheckStateChanged
7
8// CheckedChanged fires FIRST, then CheckStateChanged

Common Pitfalls

  • Missing state transitions with CheckedChanged: In three-state mode, CheckedChanged does not fire for Checked ↔ Indeterminate because Checked stays true. If you need all transitions, use CheckStateChanged.
  • Infinite event loops: Programmatically setting Checked or CheckState fires the change event. If your event handler sets another checkbox, which fires its event, you get an infinite loop. Use a _updating boolean flag to break the cycle.
  • Subscribing to both events: Subscribing to both CheckedChanged and CheckStateChanged causes double-handling in two-state mode (both fire on every click). Choose one based on whether you need two-state or three-state support.
  • Forgetting ThreeState = true: Without ThreeState = true, the checkbox only cycles between Checked and Unchecked. Setting CheckState = Indeterminate programmatically works, but the user cannot reach it by clicking.
  • Initial state firing events: Setting Checked = true in the constructor or Load event fires CheckedChanged. If your handler calls methods that depend on other controls being initialized, this causes NullReferenceException. Either subscribe to the event after initialization or guard against null references in the handler.

Summary

  • CheckedChanged fires when Checked (bool) changes — use for simple on/off toggles
  • CheckStateChanged fires when CheckState (enum) changes — use for three-state checkboxes
  • In two-state mode, both events fire identically on every click
  • In three-state mode, CheckedChanged misses the Checked ↔ Indeterminate transition
  • Use CheckStateChanged for "Select All" patterns with indeterminate state
  • Use a boolean flag (_updating) to prevent infinite event loops when programmatically setting checkbox state

Course illustration
Course illustration

All Rights Reserved.