Skip to content

CheckBox and RadioButton do not raise the UIA events for their state changes (ToggleState, ElementSelected), unlike on .NET Framework #15201

Description

@kastwey

.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);
  1. 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.
  2. Focus the check box and press Space.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

untriagedThe team needs to look at this issue in the next triage

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions