瀏覽代碼

Merge pull request #1827 from siegfriedpammer/dev/siegfriedpammer/radiobutton-groupname

Implement GroupName for RadioButton
Jeremy Koritzinsky 7 年之前
父節點
當前提交
e03ed449f2

+ 13 - 0
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@@ -22,6 +22,19 @@
         <RadioButton IsChecked="{x:Null}" IsThreeState="True">Three States: Option 3</RadioButton>
         <RadioButton IsChecked="{x:Null}" IsThreeState="True" IsEnabled="False">Disabled</RadioButton>
       </StackPanel>
+      <StackPanel Orientation="Vertical"
+                  Spacing="16">
+        <RadioButton GroupName="A" IsChecked="True">Group A: Option 1</RadioButton>
+        <RadioButton GroupName="A" IsEnabled="False">Group A: Disabled</RadioButton>
+        <RadioButton GroupName="B">Group B: Option 1</RadioButton>
+        <RadioButton GroupName="B" IsChecked="{x:Null}">Group B: Option 3</RadioButton>
+      </StackPanel>
+      <StackPanel Orientation="Vertical"
+                  Spacing="16">
+        <RadioButton GroupName="A" IsChecked="True">Group A: Option 2</RadioButton>
+        <RadioButton GroupName="B">Group B: Option 2</RadioButton>
+        <RadioButton GroupName="B" IsChecked="{x:Null}">Group B: Option 4</RadioButton>
+      </StackPanel>
     </StackPanel>
   </StackPanel>
 </UserControl>

+ 166 - 10
src/Avalonia.Controls/RadioButton.cs

@@ -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);
                 }
             }
         }

+ 38 - 0
tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs

@@ -32,5 +32,43 @@ namespace Avalonia.Controls.UnitTests
             Assert.True(radioButton1.IsChecked);
             Assert.Null(radioButton2.IsChecked);
         }
+
+        [Fact]
+        public void RadioButton_In_Same_Group_Is_Unchecked()
+        {
+            var parent = new Panel();
+
+            var panel1 = new Panel();
+            var panel2 = new Panel();
+
+            parent.Children.Add(panel1);
+            parent.Children.Add(panel2);
+
+            var radioButton1 = new RadioButton();
+            radioButton1.GroupName = "A";
+            radioButton1.IsChecked = false;
+
+            var radioButton2 = new RadioButton();
+            radioButton2.GroupName = "A";
+            radioButton2.IsChecked = true;
+
+            var radioButton3 = new RadioButton();
+            radioButton3.GroupName = "A";
+            radioButton3.IsChecked = false;
+
+            panel1.Children.Add(radioButton1);
+            panel1.Children.Add(radioButton2);
+            panel2.Children.Add(radioButton3);
+
+            Assert.False(radioButton1.IsChecked);
+            Assert.True(radioButton2.IsChecked);
+            Assert.False(radioButton3.IsChecked);
+
+            radioButton3.IsChecked = true;
+
+            Assert.False(radioButton1.IsChecked);
+            Assert.False(radioButton2.IsChecked);
+            Assert.True(radioButton3.IsChecked);
+        }
     }
 }