.NET version
.NET 10 (10.0.12) and .NET 9 (9.0.20). Same code in main.
Did it work in .NET Framework?
Yes
Did it work in any of the earlier releases of .NET Core or .NET 5+?
.NET Core 3.1, going by the code (I measured .NET Framework 4.8, .NET 9 and .NET 10): there CheckBox and RadioButton had no UIA provider of their own, as on .NET Framework. It stopped in .NET 5, when #3228 and #3244 added their UIA providers.
Issue description
When a CheckBox is checked or unchecked, or a RadioButton is checked, WinForms raises no UI Automation event that tells a client what changed. I am a blind developer, and I found it while looking into #15176.
The UI Automation specification lists these events as required:
- CheckBox control type: the
ToggleState property-changed event.
- RadioButton control type:
ElementSelected and ElementRemovedFromSelection, when the control supports the SelectionItem pattern, as RadioButtonAccessibleObject does.
What CheckBox.NotifyAccessibilityStateChanged and RadioButton.OnCheckedChanged raise on the UIA side is:
using var nameVariant = (VARIANT)Name;
AccessibilityObject.RaiseAutomationPropertyChangedEvent(UIA_PROPERTY_ID.UIA_NamePropertyId, nameVariant, nameVariant);
AccessibilityObject.RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationPropertyChangedEventId);
That is a Name change whose old and new values are the same, and UIA_AutomationPropertyChangedEventId raised as a plain automation event. Neither says that the state changed. ToolStripButton already does it right: it raises the ToggleState change with its old and new values (ToolStripButtonAccessibleObject.OnCheckStateChanged).
On .NET Framework 4.8 and .NET Core 3.1 these controls had no UIA provider of their own, so UI Automation served them through its MSAA proxy, which turned their MSAA state change into exactly these events. Since .NET 5 (#3228, #3244) WinForms serves its own UIA provider for them, so the proxy is no longer involved, and that provider does not raise them. RadioButton raised an IsSelected property change in .NET 5 and 6 (with the old and new values swapped); #5766 replaced it with the Name change above to fix #5235, where Narrator did not read radio buttons fully when moving with the arrow keys.
Screen readers on Windows 11 today: Narrator (UIA only) and JAWS 2026 announce the change indirectly, re-reading the whole control when the Name change arrives. NVDA uses MSAA for these windows, so it depends on the MSAA state change instead (see #15176).
Measurements: the events seen from another process
The attached app, with the keys sent from another process: Space on the check box "Notifications", Space three times on the three-state check box "Sync mode", Down twice in the radio group, then the timed change while the focus is on the radio group. Only UIA events are shown. Name changes are left out, except in the .NET 10 block, where they are the only events.
.NET Framework 4.8 (4.8.9325.0). The MSAA proxy raises one ToggleState change for each MSAA state change, the pressed state included, so there are several per key press, and the old values are null because the proxy does not know them:
==== Space on the check box
UIA PropertyChanged ToggleStateProperty: "Notifications" old=null new=0
UIA PropertyChanged ToggleStateProperty: "Notifications" old=null new=1
UIA PropertyChanged ToggleStateProperty: "Notifications" old=null new=1
==== Down (radio group)
UIA FocusChanged: RadioButton "Medium"
UIA ElementRemovedFromSelectionEvent: "Small"
UIA ElementSelectedEvent: "Medium"
==== timed change, focus on "Large"
UIA PropertyChanged ToggleStateProperty: "Notifications" old=null new=0
UIA PropertyChanged ToggleStateProperty: "Sync mode" old=null new=1
UIA ElementRemovedFromSelectionEvent: "Large"
UIA ElementSelectedEvent: "Small"
.NET 10.0.12 (the same on 9.0.20):
==== Space on the check box
UIA PropertyChanged NameProperty: "Notifications" old= new=
==== Space on the three-state box (1)
UIA PropertyChanged NameProperty: "Sync mode" old= new=
==== Down (radio group)
UIA FocusChanged: RadioButton "Medium"
UIA PropertyChanged NameProperty: "Small" old= new=
UIA PropertyChanged NameProperty: "Medium" old= new=
==== timed change, focus on "Large"
UIA PropertyChanged NameProperty: "Notifications" old= new=
UIA PropertyChanged NameProperty: "Sync mode" old= new=
UIA PropertyChanged NameProperty: "Large" old= new=
UIA PropertyChanged NameProperty: "Small" old= new=
.NET 10 with the change I am proposing:
==== Space on the check box
UIA PropertyChanged ToggleStateProperty: "Notifications" old=0 new=1
==== Space on the three-state box (1), (2), (3)
UIA PropertyChanged ToggleStateProperty: "Sync mode" old=0 new=1
UIA PropertyChanged ToggleStateProperty: "Sync mode" old=1 new=2
UIA PropertyChanged ToggleStateProperty: "Sync mode" old=2 new=0
==== Down (radio group)
UIA FocusChanged: RadioButton "Medium"
UIA ElementRemovedFromSelectionEvent: "Small"
UIA ElementSelectedEvent: "Medium"
==== timed change, focus on "Large"
UIA PropertyChanged ToggleStateProperty: "Notifications" old=1 new=0
UIA PropertyChanged ToggleStateProperty: "Sync mode" old=0 new=1
UIA ElementRemovedFromSelectionEvent: "Large"
UIA ElementSelectedEvent: "Small"
I have the change with unit tests ready and will open a pull request.
Steps to reproduce
WinFormsStateEventsRepro.zip
The attached app (source only, dotnet run, targets .NET 10; its README says how to switch it to net48 or net9.0-windows to compare) has a check box, a three-state check box, a radio group and a button that changes all the states by code 5 seconds later. Its README has the full steps. In short:
CheckBox checkBox = new() { Text = "&Notifications", AutoSize = true };
Controls.Add(checkBox);
- Listen to UIA property-changed events for
ToggleState on the window, for example with Accessibility Insights for Windows (event mode) or the AccEvent tool of the Windows SDK.
- Focus the check box and press Space.
- No
ToggleState change is raised; only a Name change with the same value.
The same with a RadioButton and the SelectionItem events: checking another radio button of a group raises neither ElementSelected nor ElementRemovedFromSelection.
.NET version
.NET 10 (10.0.12) and .NET 9 (9.0.20). Same code in
main.Did it work in .NET Framework?
Yes
Did it work in any of the earlier releases of .NET Core or .NET 5+?
.NET Core 3.1, going by the code (I measured .NET Framework 4.8, .NET 9 and .NET 10): there
CheckBoxandRadioButtonhad no UIA provider of their own, as on .NET Framework. It stopped in .NET 5, when #3228 and #3244 added their UIA providers.Issue description
When a
CheckBoxis checked or unchecked, or aRadioButtonis checked, WinForms raises no UI Automation event that tells a client what changed. I am a blind developer, and I found it while looking into #15176.The UI Automation specification lists these events as required:
ToggleStateproperty-changed event.ElementSelectedandElementRemovedFromSelection, when the control supports the SelectionItem pattern, asRadioButtonAccessibleObjectdoes.What
CheckBox.NotifyAccessibilityStateChangedandRadioButton.OnCheckedChangedraise on the UIA side is:That is a
Namechange whose old and new values are the same, andUIA_AutomationPropertyChangedEventIdraised as a plain automation event. Neither says that the state changed.ToolStripButtonalready does it right: it raises theToggleStatechange with its old and new values (ToolStripButtonAccessibleObject.OnCheckStateChanged).On .NET Framework 4.8 and .NET Core 3.1 these controls had no UIA provider of their own, so UI Automation served them through its MSAA proxy, which turned their MSAA state change into exactly these events. Since .NET 5 (#3228, #3244) WinForms serves its own UIA provider for them, so the proxy is no longer involved, and that provider does not raise them.
RadioButtonraised anIsSelectedproperty change in .NET 5 and 6 (with the old and new values swapped); #5766 replaced it with theNamechange above to fix #5235, where Narrator did not read radio buttons fully when moving with the arrow keys.Screen readers on Windows 11 today: Narrator (UIA only) and JAWS 2026 announce the change indirectly, re-reading the whole control when the
Namechange arrives. NVDA uses MSAA for these windows, so it depends on the MSAA state change instead (see #15176).Measurements: the events seen from another process
The attached app, with the keys sent from another process: Space on the check box "Notifications", Space three times on the three-state check box "Sync mode", Down twice in the radio group, then the timed change while the focus is on the radio group. Only UIA events are shown.
Namechanges are left out, except in the .NET 10 block, where they are the only events..NET Framework 4.8 (4.8.9325.0). The MSAA proxy raises one
ToggleStatechange for each MSAA state change, the pressed state included, so there are several per key press, and the old values are null because the proxy does not know them:.NET 10.0.12 (the same on 9.0.20):
.NET 10 with the change I am proposing:
I have the change with unit tests ready and will open a pull request.
Steps to reproduce
WinFormsStateEventsRepro.zip
The attached app (source only,
dotnet run, targets .NET 10; its README says how to switch it tonet48ornet9.0-windowsto compare) has a check box, a three-state check box, a radio group and a button that changes all the states by code 5 seconds later. Its README has the full steps. In short:ToggleStateon the window, for example with Accessibility Insights for Windows (event mode) or theAccEventtool of the Windows SDK.ToggleStatechange is raised; only aNamechange with the same value.The same with a
RadioButtonand the SelectionItem events: checking another radio button of a group raises neitherElementSelectednorElementRemovedFromSelection.