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 = true — CheckStateChanged 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
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:
| CheckState | Checked |
Unchecked | false |
Checked | true |
Indeterminate | true |
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)
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
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
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
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
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
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