|
|
@@ -2,19 +2,119 @@
|
|
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
|
|
|
|
using System;
|
|
|
+using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
using Avalonia.Controls.Primitives;
|
|
|
+using Avalonia.Rendering;
|
|
|
using Avalonia.VisualTree;
|
|
|
|
|
|
namespace Avalonia.Controls
|
|
|
{
|
|
|
public class RadioButton : ToggleButton
|
|
|
{
|
|
|
+ private class RadioButtonGroupManager
|
|
|
+ {
|
|
|
+ public static readonly RadioButtonGroupManager Default = new RadioButtonGroupManager();
|
|
|
+ static readonly ConditionalWeakTable<IRenderRoot, RadioButtonGroupManager> s_registeredVisualRoots
|
|
|
+ = new ConditionalWeakTable<IRenderRoot, RadioButtonGroupManager>();
|
|
|
+
|
|
|
+ readonly Dictionary<string, List<WeakReference<RadioButton>>> s_registeredGroups
|
|
|
+ = new Dictionary<string, List<WeakReference<RadioButton>>>();
|
|
|
+
|
|
|
+ public static RadioButtonGroupManager GetOrCreateForRoot(IRenderRoot root)
|
|
|
+ {
|
|
|
+ if (root == null)
|
|
|
+ return Default;
|
|
|
+ return s_registeredVisualRoots.GetValue(root, key => new RadioButtonGroupManager());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Add(RadioButton radioButton)
|
|
|
+ {
|
|
|
+ lock (s_registeredGroups)
|
|
|
+ {
|
|
|
+ string groupName = radioButton.GroupName;
|
|
|
+ if (!s_registeredGroups.TryGetValue(groupName, out var group))
|
|
|
+ {
|
|
|
+ group = new List<WeakReference<RadioButton>>();
|
|
|
+ s_registeredGroups.Add(groupName, group);
|
|
|
+ }
|
|
|
+ group.Add(new WeakReference<RadioButton>(radioButton));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Remove(RadioButton radioButton, string oldGroupName)
|
|
|
+ {
|
|
|
+ lock (s_registeredGroups)
|
|
|
+ {
|
|
|
+ if (!string.IsNullOrEmpty(oldGroupName) && s_registeredGroups.TryGetValue(oldGroupName, out var group))
|
|
|
+ {
|
|
|
+ int i = 0;
|
|
|
+ while (i < group.Count)
|
|
|
+ {
|
|
|
+ if (!group[i].TryGetTarget(out var button) || button == radioButton)
|
|
|
+ {
|
|
|
+ group.RemoveAt(i);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ if (group.Count == 0)
|
|
|
+ {
|
|
|
+ s_registeredGroups.Remove(oldGroupName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void SetChecked(RadioButton radioButton)
|
|
|
+ {
|
|
|
+ lock (s_registeredGroups)
|
|
|
+ {
|
|
|
+ string groupName = radioButton.GroupName;
|
|
|
+ if (s_registeredGroups.TryGetValue(groupName, out var group))
|
|
|
+ {
|
|
|
+ int i = 0;
|
|
|
+ while (i < group.Count)
|
|
|
+ {
|
|
|
+ if (!group[i].TryGetTarget(out var current))
|
|
|
+ {
|
|
|
+ group.RemoveAt(i);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (current != radioButton && current.IsChecked.GetValueOrDefault())
|
|
|
+ current.IsChecked = false;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ if (group.Count == 0)
|
|
|
+ {
|
|
|
+ s_registeredGroups.Remove(groupName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static readonly DirectProperty<RadioButton, string> GroupNameProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<RadioButton, string>(
|
|
|
+ nameof(GroupName),
|
|
|
+ o => o.GroupName,
|
|
|
+ (o, v) => o.GroupName = v);
|
|
|
+
|
|
|
+ private string _groupName;
|
|
|
+ private RadioButtonGroupManager _groupManager;
|
|
|
+
|
|
|
public RadioButton()
|
|
|
{
|
|
|
this.GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged);
|
|
|
}
|
|
|
|
|
|
+ public string GroupName
|
|
|
+ {
|
|
|
+ get { return _groupName; }
|
|
|
+ set { SetGroupName(value); }
|
|
|
+ }
|
|
|
+
|
|
|
protected override void Toggle()
|
|
|
{
|
|
|
if (!IsChecked.GetValueOrDefault())
|
|
|
@@ -23,21 +123,77 @@ namespace Avalonia.Controls
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void IsCheckedChanged(bool? value)
|
|
|
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
+ {
|
|
|
+ if (!string.IsNullOrEmpty(GroupName))
|
|
|
+ {
|
|
|
+ var manager = RadioButtonGroupManager.GetOrCreateForRoot(e.Root);
|
|
|
+ if (manager != _groupManager)
|
|
|
+ {
|
|
|
+ _groupManager.Remove(this, _groupName);
|
|
|
+ _groupManager = manager;
|
|
|
+ manager.Add(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ base.OnAttachedToVisualTree(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnDetachedFromVisualTree(e);
|
|
|
+ if (!string.IsNullOrEmpty(GroupName) && _groupManager != null)
|
|
|
+ {
|
|
|
+ _groupManager.Remove(this, _groupName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void SetGroupName(string newGroupName)
|
|
|
{
|
|
|
- var parent = this.GetVisualParent();
|
|
|
+ string oldGroupName = GroupName;
|
|
|
+ if (newGroupName != oldGroupName)
|
|
|
+ {
|
|
|
+ if (!string.IsNullOrEmpty(oldGroupName) && _groupManager != null)
|
|
|
+ {
|
|
|
+ _groupManager.Remove(this, oldGroupName);
|
|
|
+ }
|
|
|
+ _groupName = newGroupName;
|
|
|
+ if (!string.IsNullOrEmpty(newGroupName))
|
|
|
+ {
|
|
|
+ if (_groupManager == null)
|
|
|
+ {
|
|
|
+ _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(this.GetVisualRoot());
|
|
|
+ }
|
|
|
+ _groupManager.Add(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (value.GetValueOrDefault() && parent != null)
|
|
|
+ private void IsCheckedChanged(bool? value)
|
|
|
+ {
|
|
|
+ string groupName = GroupName;
|
|
|
+ if (string.IsNullOrEmpty(groupName))
|
|
|
{
|
|
|
- var siblings = parent
|
|
|
- .GetVisualChildren()
|
|
|
- .OfType<RadioButton>()
|
|
|
- .Where(x => x != this);
|
|
|
+ var parent = this.GetVisualParent();
|
|
|
|
|
|
- foreach (var sibling in siblings)
|
|
|
+ if (value.GetValueOrDefault() && parent != null)
|
|
|
+ {
|
|
|
+ var siblings = parent
|
|
|
+ .GetVisualChildren()
|
|
|
+ .OfType<RadioButton>()
|
|
|
+ .Where(x => x != this);
|
|
|
+
|
|
|
+ foreach (var sibling in siblings)
|
|
|
+ {
|
|
|
+ if (sibling.IsChecked.GetValueOrDefault())
|
|
|
+ sibling.IsChecked = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (value.GetValueOrDefault() && _groupManager != null)
|
|
|
{
|
|
|
- if (sibling.IsChecked.GetValueOrDefault())
|
|
|
- sibling.IsChecked = false;
|
|
|
+ _groupManager.SetChecked(this);
|
|
|
}
|
|
|
}
|
|
|
}
|