From 06014f29c889407f5c291ddccbac21162b24905a Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Tue, 7 Jan 2025 23:48:16 +0100 Subject: [PATCH 01/10] Move from Hashtable to Dictionary --- .../System/Windows/Controls/RadioButton.cs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index 2d48bd85d49..08207181a53 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -67,14 +67,11 @@ private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyCha private static void Register(string groupName, RadioButton radioButton) { - if (_groupNameToElements == null) - _groupNameToElements = new Hashtable(1); + _groupNameToElements ??= new Dictionary(1); lock (_groupNameToElements) { - ArrayList elements = (ArrayList)_groupNameToElements[groupName]; - - if (elements == null) + if (!_groupNameToElements.TryGetValue(groupName, out ArrayList elements)) { elements = new ArrayList(1); _groupNameToElements[groupName] = elements; @@ -92,15 +89,13 @@ private static void Register(string groupName, RadioButton radioButton) private static void Unregister(string groupName, RadioButton radioButton) { - if (_groupNameToElements == null) + if (_groupNameToElements is null) return; lock (_groupNameToElements) { // Get all elements bound to this key and remove this element - ArrayList elements = (ArrayList)_groupNameToElements[groupName]; - - if (elements != null) + if (_groupNameToElements.TryGetValue(groupName, out ArrayList elements)) { PurgeDead(elements, radioButton); if (elements.Count == 0) @@ -135,12 +130,14 @@ private void UpdateRadioButtonGroup() if (!string.IsNullOrEmpty(groupName)) { Visual rootScope = KeyboardNavigation.GetVisualRoot(this); - if (_groupNameToElements == null) - _groupNameToElements = new Hashtable(1); + + _groupNameToElements ??= new Dictionary(1); + lock (_groupNameToElements) { // Get all elements bound to this key and remove this element - ArrayList elements = (ArrayList)_groupNameToElements[groupName]; + ArrayList elements = _groupNameToElements[groupName]; + // Haha, notice the unlikely but possible null-deref here for (int i = 0; i < elements.Count; ) { WeakReference weakReference = (WeakReference)elements[i]; @@ -283,7 +280,9 @@ internal override DependencyObjectType DTypeThemeStyleKey #region private data - [ThreadStatic] private static Hashtable _groupNameToElements; + [ThreadStatic] + private static Dictionary _groupNameToElements; + private static readonly UncommonField _currentlyRegisteredGroupName = new UncommonField(); #endregion private data From df27e74b5c82799a8f705031b037c04f84c4b8eb Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Tue, 7 Jan 2025 23:51:52 +0100 Subject: [PATCH 02/10] Move from ArrayList to List --- .../System/Windows/Controls/RadioButton.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index 08207181a53..9c23a440d2a 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -67,13 +67,13 @@ private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyCha private static void Register(string groupName, RadioButton radioButton) { - _groupNameToElements ??= new Dictionary(1); + _groupNameToElements ??= new Dictionary>(1); lock (_groupNameToElements) { - if (!_groupNameToElements.TryGetValue(groupName, out ArrayList elements)) + if (!_groupNameToElements.TryGetValue(groupName, out List elements)) { - elements = new ArrayList(1); + elements = new List(1); _groupNameToElements[groupName] = elements; } else @@ -95,7 +95,7 @@ private static void Unregister(string groupName, RadioButton radioButton) lock (_groupNameToElements) { // Get all elements bound to this key and remove this element - if (_groupNameToElements.TryGetValue(groupName, out ArrayList elements)) + if (_groupNameToElements.TryGetValue(groupName, out List elements)) { PurgeDead(elements, radioButton); if (elements.Count == 0) @@ -107,11 +107,11 @@ private static void Unregister(string groupName, RadioButton radioButton) _currentlyRegisteredGroupName.SetValue(radioButton, null); } - private static void PurgeDead(ArrayList elements, object elementToRemove) + private static void PurgeDead(List elements, object elementToRemove) { for (int i = 0; i < elements.Count; ) { - WeakReference weakReference = (WeakReference)elements[i]; + WeakReference weakReference = elements[i]; object element = weakReference.Target; if (element == null || element == elementToRemove) { @@ -131,16 +131,16 @@ private void UpdateRadioButtonGroup() { Visual rootScope = KeyboardNavigation.GetVisualRoot(this); - _groupNameToElements ??= new Dictionary(1); + _groupNameToElements ??= new Dictionary>(1); lock (_groupNameToElements) { // Get all elements bound to this key and remove this element - ArrayList elements = _groupNameToElements[groupName]; + List elements = _groupNameToElements[groupName]; // Haha, notice the unlikely but possible null-deref here for (int i = 0; i < elements.Count; ) { - WeakReference weakReference = (WeakReference)elements[i]; + WeakReference weakReference = elements[i]; RadioButton rb = weakReference.Target as RadioButton; if (rb == null) { @@ -281,7 +281,7 @@ internal override DependencyObjectType DTypeThemeStyleKey #region private data [ThreadStatic] - private static Dictionary _groupNameToElements; + private static Dictionary> _groupNameToElements; private static readonly UncommonField _currentlyRegisteredGroupName = new UncommonField(); From 101de4a6084c13b9c469255042cf73436ba69564 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Tue, 7 Jan 2025 23:57:19 +0100 Subject: [PATCH 03/10] Use generic WeakReference --- .../System/Windows/Controls/RadioButton.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index 9c23a440d2a..fdf2c823744 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -67,13 +67,13 @@ private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyCha private static void Register(string groupName, RadioButton radioButton) { - _groupNameToElements ??= new Dictionary>(1); + _groupNameToElements ??= new Dictionary>>(1); lock (_groupNameToElements) { - if (!_groupNameToElements.TryGetValue(groupName, out List elements)) + if (!_groupNameToElements.TryGetValue(groupName, out List> elements)) { - elements = new List(1); + elements = new List>(1); _groupNameToElements[groupName] = elements; } else @@ -82,7 +82,7 @@ private static void Register(string groupName, RadioButton radioButton) PurgeDead(elements, null); } - elements.Add(new WeakReference(radioButton)); + elements.Add(new WeakReference(radioButton)); } _currentlyRegisteredGroupName.SetValue(radioButton, groupName); } @@ -95,7 +95,7 @@ private static void Unregister(string groupName, RadioButton radioButton) lock (_groupNameToElements) { // Get all elements bound to this key and remove this element - if (_groupNameToElements.TryGetValue(groupName, out List elements)) + if (_groupNameToElements.TryGetValue(groupName, out List> elements)) { PurgeDead(elements, radioButton); if (elements.Count == 0) @@ -107,13 +107,12 @@ private static void Unregister(string groupName, RadioButton radioButton) _currentlyRegisteredGroupName.SetValue(radioButton, null); } - private static void PurgeDead(List elements, object elementToRemove) + private static void PurgeDead(List> elements, RadioButton elementToRemove) { for (int i = 0; i < elements.Count; ) { - WeakReference weakReference = elements[i]; - object element = weakReference.Target; - if (element == null || element == elementToRemove) + WeakReference weakReference = elements[i]; + if (!weakReference.TryGetTarget(out RadioButton element) || element == elementToRemove) { elements.RemoveAt(i); } @@ -131,18 +130,17 @@ private void UpdateRadioButtonGroup() { Visual rootScope = KeyboardNavigation.GetVisualRoot(this); - _groupNameToElements ??= new Dictionary>(1); + _groupNameToElements ??= new Dictionary>>(1); lock (_groupNameToElements) { // Get all elements bound to this key and remove this element - List elements = _groupNameToElements[groupName]; + List> elements = _groupNameToElements[groupName]; // Haha, notice the unlikely but possible null-deref here for (int i = 0; i < elements.Count; ) { - WeakReference weakReference = elements[i]; - RadioButton rb = weakReference.Target as RadioButton; - if (rb == null) + WeakReference weakReference = elements[i]; + if (!weakReference.TryGetTarget(out RadioButton rb)) { // Remove dead instances elements.RemoveAt(i); @@ -281,7 +279,7 @@ internal override DependencyObjectType DTypeThemeStyleKey #region private data [ThreadStatic] - private static Dictionary> _groupNameToElements; + private static Dictionary>> _groupNameToElements; private static readonly UncommonField _currentlyRegisteredGroupName = new UncommonField(); From 16fe3b34573eec39886b35da27441aff3f5f2e6d Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Wed, 8 Jan 2025 00:03:45 +0100 Subject: [PATCH 04/10] Remove locks as its thread-bound instance --- .../System/Windows/Controls/RadioButton.cs | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index fdf2c823744..1d5c4b96663 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -69,41 +69,39 @@ private static void Register(string groupName, RadioButton radioButton) { _groupNameToElements ??= new Dictionary>>(1); - lock (_groupNameToElements) + if (!_groupNameToElements.TryGetValue(groupName, out List> elements)) { - if (!_groupNameToElements.TryGetValue(groupName, out List> elements)) - { - elements = new List>(1); - _groupNameToElements[groupName] = elements; - } - else - { - // There were some elements there, remove dead ones - PurgeDead(elements, null); - } - - elements.Add(new WeakReference(radioButton)); + elements = new List>(1); + _groupNameToElements[groupName] = elements; } + else + { + // There were some elements there, remove dead ones + PurgeDead(elements, null); + } + + elements.Add(new WeakReference(radioButton)); + _currentlyRegisteredGroupName.SetValue(radioButton, groupName); } private static void Unregister(string groupName, RadioButton radioButton) { + Debug.Assert(_groupNameToElements is not null, "Unregister was called before Register"); + if (_groupNameToElements is null) return; - lock (_groupNameToElements) + // Get all elements bound to this key and remove this element + if (_groupNameToElements.TryGetValue(groupName, out List> elements)) { - // Get all elements bound to this key and remove this element - if (_groupNameToElements.TryGetValue(groupName, out List> elements)) - { - PurgeDead(elements, radioButton); - if (elements.Count == 0) - { - _groupNameToElements.Remove(groupName); - } - } + PurgeDead(elements, radioButton); + + // If the group has zero elements, remove it + if (elements.Count == 0) + _groupNameToElements.Remove(groupName); } + _currentlyRegisteredGroupName.SetValue(radioButton, null); } @@ -132,11 +130,9 @@ private void UpdateRadioButtonGroup() _groupNameToElements ??= new Dictionary>>(1); - lock (_groupNameToElements) + // Get all elements bound to this key and remove this element + if (_groupNameToElements.TryGetValue(groupName, out List> elements)) { - // Get all elements bound to this key and remove this element - List> elements = _groupNameToElements[groupName]; - // Haha, notice the unlikely but possible null-deref here for (int i = 0; i < elements.Count; ) { WeakReference weakReference = elements[i]; From 510c3dfa92381fd515a5448f105c94637007f58f Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Wed, 8 Jan 2025 00:07:50 +0100 Subject: [PATCH 05/10] Count downwards like regular people, don't branch --- .../System/Windows/Controls/RadioButton.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index 1d5c4b96663..f7dc2778d30 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -107,17 +107,12 @@ private static void Unregister(string groupName, RadioButton radioButton) private static void PurgeDead(List> elements, RadioButton elementToRemove) { - for (int i = 0; i < elements.Count; ) + for (int i = elements.Count - 1; i >= 0; i--) { - WeakReference weakReference = elements[i]; - if (!weakReference.TryGetTarget(out RadioButton element) || element == elementToRemove) + if (!elements[i].TryGetTarget(out RadioButton element) || element == elementToRemove) { elements.RemoveAt(i); } - else - { - i++; - } } } @@ -133,20 +128,18 @@ private void UpdateRadioButtonGroup() // Get all elements bound to this key and remove this element if (_groupNameToElements.TryGetValue(groupName, out List> elements)) { - for (int i = 0; i < elements.Count; ) + for (int i = elements.Count - 1; i >= 0; i--) { - WeakReference weakReference = elements[i]; - if (!weakReference.TryGetTarget(out RadioButton rb)) + if (elements[i].TryGetTarget(out RadioButton radioButton)) { - // Remove dead instances - elements.RemoveAt(i); + // Uncheck all checked RadioButtons different from the current one + if (radioButton != this && radioButton.IsChecked is true && rootScope == KeyboardNavigation.GetVisualRoot(radioButton)) + radioButton.UncheckRadioButton(); } else { - // Uncheck all checked RadioButtons different from the current one - if (rb != this && (rb.IsChecked == true) && rootScope == KeyboardNavigation.GetVisualRoot(rb)) - rb.UncheckRadioButton(); - i++; + // Remove dead instances + elements.RemoveAt(i); } } } From c1ddc5f5411273b871ee56dab88946398ef3b700 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Wed, 8 Jan 2025 00:08:38 +0100 Subject: [PATCH 06/10] Imho the standard number of RadioButtons is at least 2 --- .../System/Windows/Controls/RadioButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index f7dc2778d30..8a61a94fa5a 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -71,7 +71,7 @@ private static void Register(string groupName, RadioButton radioButton) if (!_groupNameToElements.TryGetValue(groupName, out List> elements)) { - elements = new List>(1); + elements = new List>(2); _groupNameToElements[groupName] = elements; } else From 2bdd9764c66455ab1a8d7ce504a28a89fec2fd83 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Wed, 8 Jan 2025 00:09:25 +0100 Subject: [PATCH 07/10] Add t_ prefix according to conventions --- .../System/Windows/Controls/RadioButton.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index 8a61a94fa5a..398402ecb4f 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -67,12 +67,12 @@ private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyCha private static void Register(string groupName, RadioButton radioButton) { - _groupNameToElements ??= new Dictionary>>(1); + t_groupNameToElements ??= new Dictionary>>(1); - if (!_groupNameToElements.TryGetValue(groupName, out List> elements)) + if (!t_groupNameToElements.TryGetValue(groupName, out List> elements)) { elements = new List>(2); - _groupNameToElements[groupName] = elements; + t_groupNameToElements[groupName] = elements; } else { @@ -87,19 +87,19 @@ private static void Register(string groupName, RadioButton radioButton) private static void Unregister(string groupName, RadioButton radioButton) { - Debug.Assert(_groupNameToElements is not null, "Unregister was called before Register"); + Debug.Assert(t_groupNameToElements is not null, "Unregister was called before Register"); - if (_groupNameToElements is null) + if (t_groupNameToElements is null) return; // Get all elements bound to this key and remove this element - if (_groupNameToElements.TryGetValue(groupName, out List> elements)) + if (t_groupNameToElements.TryGetValue(groupName, out List> elements)) { PurgeDead(elements, radioButton); // If the group has zero elements, remove it if (elements.Count == 0) - _groupNameToElements.Remove(groupName); + t_groupNameToElements.Remove(groupName); } _currentlyRegisteredGroupName.SetValue(radioButton, null); @@ -123,10 +123,10 @@ private void UpdateRadioButtonGroup() { Visual rootScope = KeyboardNavigation.GetVisualRoot(this); - _groupNameToElements ??= new Dictionary>>(1); + t_groupNameToElements ??= new Dictionary>>(1); // Get all elements bound to this key and remove this element - if (_groupNameToElements.TryGetValue(groupName, out List> elements)) + if (t_groupNameToElements.TryGetValue(groupName, out List> elements)) { for (int i = elements.Count - 1; i >= 0; i--) { @@ -268,7 +268,7 @@ internal override DependencyObjectType DTypeThemeStyleKey #region private data [ThreadStatic] - private static Dictionary>> _groupNameToElements; + private static Dictionary>> t_groupNameToElements; private static readonly UncommonField _currentlyRegisteredGroupName = new UncommonField(); From 87d803c6696568b952cd8873a1f9d3bdf2af087b Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Wed, 8 Jan 2025 00:14:05 +0100 Subject: [PATCH 08/10] Use optimized lookup/create for Register --- .../System/Windows/Controls/RadioButton.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index 398402ecb4f..8fe7fee625b 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -4,6 +4,7 @@ using System.Collections; using System.ComponentModel; +using System.Runtime.InteropServices; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; @@ -69,10 +70,11 @@ private static void Register(string groupName, RadioButton radioButton) { t_groupNameToElements ??= new Dictionary>>(1); - if (!t_groupNameToElements.TryGetValue(groupName, out List> elements)) + ref List> elements = ref CollectionsMarshal.GetValueRefOrAddDefault(t_groupNameToElements, groupName, out bool exists); + if (!exists) { + // Create new collection elements = new List>(2); - t_groupNameToElements[groupName] = elements; } else { From bc6ca5ec6bf74ab13c4757ac6523a35421feeef9 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Fri, 10 Jan 2025 01:59:13 +0100 Subject: [PATCH 09/10] Final cleanup --- .../System/Windows/Controls/RadioButton.cs | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index 8fe7fee625b..7569e60f435 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -1,13 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Collections; +using System.Windows.Input; using System.ComponentModel; using System.Runtime.InteropServices; +using System.Windows.Automation.Peers; using System.Windows.Controls.Primitives; -using System.Windows.Input; -using System.Windows.Media; using MS.Internal.Telemetry.PresentationFramework; // Disable CS3001: Warning as Error: not CLS-compliant @@ -123,20 +122,29 @@ private void UpdateRadioButtonGroup() string groupName = GroupName; if (!string.IsNullOrEmpty(groupName)) { - Visual rootScope = KeyboardNavigation.GetVisualRoot(this); - t_groupNameToElements ??= new Dictionary>>(1); - // Get all elements bound to this key and remove this element + // Get all elements bound to this key if (t_groupNameToElements.TryGetValue(groupName, out List> elements)) { for (int i = elements.Count - 1; i >= 0; i--) { + // Either remove the dead element or uncheck if we're checked if (elements[i].TryGetTarget(out RadioButton radioButton)) { - // Uncheck all checked RadioButtons different from the current one - if (radioButton != this && radioButton.IsChecked is true && rootScope == KeyboardNavigation.GetVisualRoot(radioButton)) + // Uncheck all checked RadioButtons but this one + if (radioButton != this && radioButton.IsChecked is true) + { + DependencyObject rootScope = KeyboardNavigation.GetVisualRoot(this); + DependencyObject otherRoot = KeyboardNavigation.GetVisualRoot(radioButton); + + // If elements have the same group name but the visual roots are different, we still treat them + // as unique since we want to promote reuse of group names to make them easier to work with. + if (rootScope != otherRoot) + continue; + radioButton.UncheckRadioButton(); + } } else { @@ -148,17 +156,19 @@ private void UpdateRadioButtonGroup() } else // Logical parent should be the group { - DependencyObject parent = this.Parent; - if (parent != null) + DependencyObject parent = Parent; + if (parent is not null) { // Traverse logical children IEnumerable children = LogicalTreeHelper.GetChildren(parent); IEnumerator itor = children.GetEnumerator(); while (itor.MoveNext()) { - RadioButton rb = itor.Current as RadioButton; - if (rb != null && rb != this && string.IsNullOrEmpty(rb.GroupName) && (rb.IsChecked == true)) - rb.UncheckRadioButton(); + if (itor.Current is RadioButton radioButton && radioButton.IsChecked is true && + radioButton != this && string.IsNullOrEmpty(radioButton.GroupName)) + { + radioButton.UncheckRadioButton(); + } } } } @@ -181,7 +191,7 @@ private void UncheckRadioButton() "GroupName", typeof(string), typeof(RadioButton), - new FrameworkPropertyMetadata(String.Empty, new PropertyChangedCallback(OnGroupNameChanged))); + new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(OnGroupNameChanged))); /// /// GroupName determine mutually excusive radiobutton groups @@ -208,9 +218,9 @@ public string GroupName /// /// Creates AutomationPeer () /// - protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() + protected override AutomationPeer OnCreateAutomationPeer() { - return new System.Windows.Automation.Peers.RadioButtonAutomationPeer(this); + return new RadioButtonAutomationPeer(this); } /// @@ -250,10 +260,6 @@ protected override void OnAccessKey(AccessKeyEventArgs e) #endregion - #region Accessibility - - #endregion Accessibility - #region DTypeThemeStyleKey // Returns the DependencyObjectType for the registered ThemeStyleKey's default @@ -263,7 +269,7 @@ internal override DependencyObjectType DTypeThemeStyleKey get { return _dType; } } - private static DependencyObjectType _dType; + private static readonly DependencyObjectType _dType; #endregion DTypeThemeStyleKey From b18bc5a9b752f033f801b89bdd01a75e75ee3d1c Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Fri, 10 Jan 2025 02:00:44 +0100 Subject: [PATCH 10/10] Fix #2995 and allow binding instance sharing under same visual root --- .../System/Windows/Controls/RadioButton.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs index 7569e60f435..1c872cb1973 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/RadioButton.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; +using System.Windows.Data; using System.Windows.Input; using System.ComponentModel; using System.Runtime.InteropServices; @@ -135,14 +136,23 @@ private void UpdateRadioButtonGroup() // Uncheck all checked RadioButtons but this one if (radioButton != this && radioButton.IsChecked is true) { - DependencyObject rootScope = KeyboardNavigation.GetVisualRoot(this); - DependencyObject otherRoot = KeyboardNavigation.GetVisualRoot(radioButton); + DependencyObject rootScope = InputElement.GetRootVisual(this); + DependencyObject otherRoot = InputElement.GetRootVisual(radioButton); // If elements have the same group name but the visual roots are different, we still treat them // as unique since we want to promote reuse of group names to make them easier to work with. if (rootScope != otherRoot) continue; + // Allow binding sharing under the same visual root + BindingExpression rootBindingSource = GetBindingExpression(IsCheckedProperty); + BindingExpression otherBindingSource = radioButton.GetBindingExpression(IsCheckedProperty); + + if (rootBindingSource is not null && otherBindingSource is not null && + rootBindingSource.SourceItem == otherBindingSource.SourceItem && + rootBindingSource.SourcePropertyName == otherBindingSource.SourcePropertyName) + continue; + radioButton.UncheckRadioButton(); } }