From 804c36e20078de00184d12f1832fc47310e47a23 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Wed, 16 Jul 2025 17:56:36 +0800 Subject: [PATCH 1/8] Add InternalRaiseAutomationNotification in WmDateChanged of the MonthCalendar --- .../Forms/Controls/MonthCalendar/MonthCalendar.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs index 84f0f5ca331..53d797a2914 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs @@ -2063,6 +2063,12 @@ private unsafe void WmDateChanged(ref Message m) _selectionStart = start; _selectionEnd = end; + PInvoke.NotifyWinEvent( + (uint)AccessibleEvents.Focus, + this, + (int)OBJECT_IDENTIFIER.OBJID_CLIENT, + (int)PInvoke.CHILDID_SELF); + AccessibilityNotifyClients(AccessibleEvents.NameChange, -1); AccessibilityNotifyClients(AccessibleEvents.ValueChange, -1); @@ -2089,6 +2095,11 @@ private unsafe void WmDateChanged(ref Message m) { MonthCalendarAccessibleObject calendarAccessibleObject = (MonthCalendarAccessibleObject)AccessibilityObject; calendarAccessibleObject.RaiseAutomationEventForChild(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId); + + AccessibilityObject.InternalRaiseAutomationNotification( + Automation.AutomationNotificationKind.Other, + Automation.AutomationNotificationProcessing.MostRecent, + calendarAccessibleObject.Value!); } OnDateChanged(new DateRangeEventArgs(start, end)); From 553996e080cc9fa434b577c938573a6e6b419495 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Thu, 17 Jul 2025 17:22:55 +0800 Subject: [PATCH 2/8] Add switch EnableMonthCalendarAutomationNotification --- .../LocalAppContextSwitches.cs | 12 +++++++- .../Controls/MonthCalendar/MonthCalendar.cs | 28 ++++++++++++------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs index 5b728b3472a..bd3de1b89db 100644 --- a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs +++ b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs @@ -26,6 +26,7 @@ internal static partial class LocalAppContextSwitches internal const string EnableMsoComponentManagerSwitchName = "Switch.System.Windows.Forms.EnableMsoComponentManager"; internal const string TreeNodeCollectionAddRangeRespectsSortOrderSwitchName = "System.Windows.Forms.TreeNodeCollectionAddRangeRespectsSortOrder"; internal const string MoveTreeViewTextLocationOnePixelSwitchName = "System.Windows.Forms.TreeView.MoveTreeViewTextLocationOnePixel"; + internal const string EnableMonthCalendarAutomationNotificationSwitchName = "System.Windows.Forms.RichTextBox.EnableMonthCalendarAutomationNotification"; private static int s_scaleTopLevelFormMinMaxSizeForDpi; private static int s_anchorLayoutV2; @@ -37,7 +38,7 @@ internal static partial class LocalAppContextSwitches private static int s_noClientNotifications; private static int s_enableMsoComponentManager; private static int s_treeNodeCollectionAddRangeRespectsSortOrder; - + private static int s_enableMonthCalendarAutomationNotification; private static int s_moveTreeViewTextLocationOnePixel; private static FrameworkName? s_targetFrameworkName; @@ -231,4 +232,13 @@ public static bool MoveTreeViewTextLocationOnePixel [MethodImpl(MethodImplOptions.AggressiveInlining)] get => GetCachedSwitchValue(MoveTreeViewTextLocationOnePixelSwitchName, ref s_moveTreeViewTextLocationOnePixel); } + + /// + /// Indicates whether the text of the RichTextBox is sent to the UIA provider. + /// + public static bool EnableMonthCalendarAutomationNotification + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetCachedSwitchValue(EnableMonthCalendarAutomationNotificationSwitchName, ref s_enableMonthCalendarAutomationNotification); + } } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs index 53d797a2914..2123303ea1d 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs @@ -2063,12 +2063,6 @@ private unsafe void WmDateChanged(ref Message m) _selectionStart = start; _selectionEnd = end; - PInvoke.NotifyWinEvent( - (uint)AccessibleEvents.Focus, - this, - (int)OBJECT_IDENTIFIER.OBJID_CLIENT, - (int)PInvoke.CHILDID_SELF); - AccessibilityNotifyClients(AccessibleEvents.NameChange, -1); AccessibilityNotifyClients(AccessibleEvents.ValueChange, -1); @@ -2096,10 +2090,24 @@ private unsafe void WmDateChanged(ref Message m) MonthCalendarAccessibleObject calendarAccessibleObject = (MonthCalendarAccessibleObject)AccessibilityObject; calendarAccessibleObject.RaiseAutomationEventForChild(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId); - AccessibilityObject.InternalRaiseAutomationNotification( - Automation.AutomationNotificationKind.Other, - Automation.AutomationNotificationProcessing.MostRecent, - calendarAccessibleObject.Value!); + // Some accessibility tools (e.g., NVDA) may not recognize calendar cells as IAccessible elements, + // which can result in the UI Automation focus change event (AutomationFocusChanged) being ignored. + if (AppContextSwitches.EnableMonthCalendarAutomationNotification) + { + // Set focus to MonthCalendar to ensure that its state changes (such as selecting an item with the arrow keys) + // are announced correctly by accessibility tools. + PInvoke.NotifyWinEvent( + (uint)AccessibleEvents.Focus, + this, + (int)OBJECT_IDENTIFIER.OBJID_CLIENT, + (int)PInvoke.CHILDID_SELF); + + // Notify accessibility tool the currently selected item has changed. + AccessibilityObject.InternalRaiseAutomationNotification( + Automation.AutomationNotificationKind.Other, + Automation.AutomationNotificationProcessing.MostRecent, + calendarAccessibleObject.Value!); + } } OnDateChanged(new DateRangeEventArgs(start, end)); From dbac5c43e4fcb5f256cdf5af0ebdaf7bd162ad19 Mon Sep 17 00:00:00 2001 From: Leaf Shi <132890443+LeafShi1@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:44:26 +0800 Subject: [PATCH 3/8] Update src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System/LocalAppContextSwitches/LocalAppContextSwitches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs index bd3de1b89db..3bb55cc3cc5 100644 --- a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs +++ b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs @@ -234,7 +234,7 @@ public static bool MoveTreeViewTextLocationOnePixel } /// - /// Indicates whether the text of the RichTextBox is sent to the UIA provider. + /// Indicates whether automation notifications for the MonthCalendar control are enabled. /// public static bool EnableMonthCalendarAutomationNotification { From 6211a1116d624686c85e7ffa3c9a6f111c1e93d8 Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Tue, 22 Jul 2025 18:27:46 +0800 Subject: [PATCH 4/8] Focus the MonthCanlendar when select date changed --- .../LocalAppContextSwitches.cs | 11 ---------- .../Controls/MonthCalendar/MonthCalendar.cs | 20 +------------------ 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs index 3bb55cc3cc5..7a8f943f1be 100644 --- a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs +++ b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs @@ -26,7 +26,6 @@ internal static partial class LocalAppContextSwitches internal const string EnableMsoComponentManagerSwitchName = "Switch.System.Windows.Forms.EnableMsoComponentManager"; internal const string TreeNodeCollectionAddRangeRespectsSortOrderSwitchName = "System.Windows.Forms.TreeNodeCollectionAddRangeRespectsSortOrder"; internal const string MoveTreeViewTextLocationOnePixelSwitchName = "System.Windows.Forms.TreeView.MoveTreeViewTextLocationOnePixel"; - internal const string EnableMonthCalendarAutomationNotificationSwitchName = "System.Windows.Forms.RichTextBox.EnableMonthCalendarAutomationNotification"; private static int s_scaleTopLevelFormMinMaxSizeForDpi; private static int s_anchorLayoutV2; @@ -38,7 +37,6 @@ internal static partial class LocalAppContextSwitches private static int s_noClientNotifications; private static int s_enableMsoComponentManager; private static int s_treeNodeCollectionAddRangeRespectsSortOrder; - private static int s_enableMonthCalendarAutomationNotification; private static int s_moveTreeViewTextLocationOnePixel; private static FrameworkName? s_targetFrameworkName; @@ -232,13 +230,4 @@ public static bool MoveTreeViewTextLocationOnePixel [MethodImpl(MethodImplOptions.AggressiveInlining)] get => GetCachedSwitchValue(MoveTreeViewTextLocationOnePixelSwitchName, ref s_moveTreeViewTextLocationOnePixel); } - - /// - /// Indicates whether automation notifications for the MonthCalendar control are enabled. - /// - public static bool EnableMonthCalendarAutomationNotification - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetCachedSwitchValue(EnableMonthCalendarAutomationNotificationSwitchName, ref s_enableMonthCalendarAutomationNotification); - } } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs index 2123303ea1d..867c7092879 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs @@ -2063,6 +2063,7 @@ private unsafe void WmDateChanged(ref Message m) _selectionStart = start; _selectionEnd = end; + AccessibilityNotifyClients(AccessibleEvents.Focus, -1); AccessibilityNotifyClients(AccessibleEvents.NameChange, -1); AccessibilityNotifyClients(AccessibleEvents.ValueChange, -1); @@ -2089,25 +2090,6 @@ private unsafe void WmDateChanged(ref Message m) { MonthCalendarAccessibleObject calendarAccessibleObject = (MonthCalendarAccessibleObject)AccessibilityObject; calendarAccessibleObject.RaiseAutomationEventForChild(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId); - - // Some accessibility tools (e.g., NVDA) may not recognize calendar cells as IAccessible elements, - // which can result in the UI Automation focus change event (AutomationFocusChanged) being ignored. - if (AppContextSwitches.EnableMonthCalendarAutomationNotification) - { - // Set focus to MonthCalendar to ensure that its state changes (such as selecting an item with the arrow keys) - // are announced correctly by accessibility tools. - PInvoke.NotifyWinEvent( - (uint)AccessibleEvents.Focus, - this, - (int)OBJECT_IDENTIFIER.OBJID_CLIENT, - (int)PInvoke.CHILDID_SELF); - - // Notify accessibility tool the currently selected item has changed. - AccessibilityObject.InternalRaiseAutomationNotification( - Automation.AutomationNotificationKind.Other, - Automation.AutomationNotificationProcessing.MostRecent, - calendarAccessibleObject.Value!); - } } OnDateChanged(new DateRangeEventArgs(start, end)); From 728b2c6b78bc40841b40ab3344f27e146e888224 Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Tue, 22 Jul 2025 19:04:22 +0800 Subject: [PATCH 5/8] Removing extra changes --- .../System/LocalAppContextSwitches/LocalAppContextSwitches.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs index 7a8f943f1be..5b728b3472a 100644 --- a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs +++ b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs @@ -37,6 +37,7 @@ internal static partial class LocalAppContextSwitches private static int s_noClientNotifications; private static int s_enableMsoComponentManager; private static int s_treeNodeCollectionAddRangeRespectsSortOrder; + private static int s_moveTreeViewTextLocationOnePixel; private static FrameworkName? s_targetFrameworkName; From acb2d5933d571cc36fbb99d4caad8d5c0be5dd56 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Wed, 23 Jul 2025 14:15:05 +0800 Subject: [PATCH 6/8] Add NotifyWinEvent in MonthCalendar --- .../Controls/MonthCalendar/MonthCalendar.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs index 867c7092879..92cd771828e 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs @@ -2063,9 +2063,9 @@ private unsafe void WmDateChanged(ref Message m) _selectionStart = start; _selectionEnd = end; - AccessibilityNotifyClients(AccessibleEvents.Focus, -1); - AccessibilityNotifyClients(AccessibleEvents.NameChange, -1); - AccessibilityNotifyClients(AccessibleEvents.ValueChange, -1); + NotifyWinEvent(AccessibleEvents.Focus); + NotifyWinEvent(AccessibleEvents.NameChange); + NotifyWinEvent(AccessibleEvents.ValueChange); // We should use the Date for comparison in this case. The user can work in the calendar only with dates, // while the minimum / maximum date can contain the date and custom time, which, when comparing Ticks, @@ -2095,6 +2095,16 @@ private unsafe void WmDateChanged(ref Message m) OnDateChanged(new DateRangeEventArgs(start, end)); } + private void NotifyWinEvent(AccessibleEvents accessibleEvent) + { + PInvoke.NotifyWinEvent( + (uint)accessibleEvent, + this, + (int)OBJECT_IDENTIFIER.OBJID_CLIENT, + (int)PInvoke.CHILDID_SELF + ); + } + /// /// Handles the MCN_GETDAYSTATE notification by returning an array of bitmasks, one entry per month, /// that specifies which dates to display in bold. From c1aa344968c39adf22260d9e1538258fc78df16b Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Wed, 6 Aug 2025 19:28:26 -0700 Subject: [PATCH 7/8] Add MSAA focus event in OnGotFocus --- .../Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs index 92cd771828e..e00dc8dd1a6 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs @@ -1428,6 +1428,8 @@ protected override void OnGotFocus(EventArgs e) { base.OnGotFocus(e); + NotifyWinEvent(AccessibleEvents.Focus); + if (IsAccessibilityObjectCreated) { ((MonthCalendarAccessibleObject)AccessibilityObject).FocusedCell?.RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId); From 3d5ae61ba2c5fd7a685b0a64aebf9852162fa891 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Tue, 23 Sep 2025 19:23:33 -0700 Subject: [PATCH 8/8] Add handle check before invoke PInvoke.NotifyWinEvent --- .../Controls/MonthCalendar/MonthCalendar.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs index e00dc8dd1a6..615a981ad36 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/MonthCalendar/MonthCalendar.cs @@ -2099,12 +2099,21 @@ private unsafe void WmDateChanged(ref Message m) private void NotifyWinEvent(AccessibleEvents accessibleEvent) { - PInvoke.NotifyWinEvent( - (uint)accessibleEvent, - this, - (int)OBJECT_IDENTIFIER.OBJID_CLIENT, - (int)PInvoke.CHILDID_SELF - ); + try + { + if (IsHandleCreated) + { + PInvoke.NotifyWinEvent( + (uint)accessibleEvent, + this, + (int)OBJECT_IDENTIFIER.OBJID_CLIENT, + (int)PInvoke.CHILDID_SELF); + } + } + catch (Exception ex) + { + Debug.Fail($"NotifyWinEvent failed: {ex.Message}"); + } } ///