Browse Source

Merge pull request #10281 from AvaloniaUI/fixes/10232-combobox-keyboard

Fix ComboBox keyboard selection when drop-down closed
Max Katz 2 years ago
parent
commit
4d4cb02858

+ 5 - 0
.ncrunch/Avalonia.UnitTests.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <XUnit2Enabled>False</XUnit2Enabled>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/GpuInterop.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 1 - 0
Avalonia.Desktop.slnf

@@ -45,6 +45,7 @@
       "tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
       "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
       "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",
+      "tests\\Avalonia.Controls.ItemsRepeater.UnitTests\\Avalonia.Controls.ItemsRepeater.UnitTests.csproj",
       "tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj",
       "tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj",
       "tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj",

+ 1 - 0
samples/IntegrationTestApp/MainWindow.axaml

@@ -70,6 +70,7 @@
             <ComboBoxItem>Item 0</ComboBoxItem>
             <ComboBoxItem>Item 1</ComboBoxItem>
           </ComboBox>
+          <CheckBox Name="ComboBoxWrapSelection" IsChecked="{Binding #BasicComboBox.WrapSelection}">Wrap Selection</CheckBox>
           <Button Name="ComboBoxSelectionClear">Clear Selection</Button>
           <Button Name="ComboBoxSelectFirst">Select First</Button>
         </StackPanel>

+ 38 - 17
src/Avalonia.Controls/ComboBox.cs

@@ -1,19 +1,19 @@
 using System;
+using System.Diagnostics;
 using System.Linq;
 using Avalonia.Automation.Peers;
-using Avalonia.Reactive;
-using Avalonia.Controls.Generators;
-using Avalonia.Controls.Mixins;
-using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Selection;
 using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Templates;
+using Avalonia.Controls.Utils;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
 using Avalonia.Media;
+using Avalonia.Reactive;
 using Avalonia.VisualTree;
-using Avalonia.Controls.Metadata;
 
 namespace Avalonia.Controls
 {
@@ -219,7 +219,7 @@ namespace Avalonia.Controls
                 }
                 else if (e.Key == Key.Up)
                 {
-                    SelectPrev();
+                    SelectPrevious();
                     e.Handled = true;
                 }
             }
@@ -250,7 +250,7 @@ namespace Avalonia.Controls
                         if (e.Delta.Y < 0)
                             SelectNext();
                         else
-                            SelectPrev();
+                            SelectPrevious();
 
                         e.Handled = true;
                     }
@@ -478,19 +478,40 @@ namespace Avalonia.Controls
             }
         }
 
-        private void SelectNext()
-        {
-            if (ItemCount >= 1)
-            {
-                MoveSelection(NavigationDirection.Next, WrapSelection);
-            }
-        }
+        private void SelectNext() => MoveSelection(SelectedIndex, 1, WrapSelection);
+        private void SelectPrevious() => MoveSelection(SelectedIndex, -1, WrapSelection);
 
-        private void SelectPrev()
+        private void MoveSelection(int startIndex, int step, bool wrap)
         {
-            if (ItemCount >= 1)
+            static bool IsSelectable(object? o) => (o as AvaloniaObject)?.GetValue(IsEnabledProperty) ?? true;
+
+            var count = ItemCount;
+
+            for (int i = startIndex + step; i != startIndex; i += step)
             {
-                MoveSelection(NavigationDirection.Previous, WrapSelection);
+                if (i < 0 || i >= count)
+                {
+                    if (wrap)
+                    {
+                        if (i < 0)
+                            i += count;
+                        else if (i >= count)
+                            i %= count;
+                    }
+                    else
+                    {
+                        return;
+                    }
+                }
+
+                var item = ItemsView[i];
+                var container = ContainerFromIndex(i);
+                
+                if (IsSelectable(item) && IsSelectable(container))
+                {
+                    SelectedIndex = i;
+                    break;
+                }
             }
         }
     }

+ 59 - 2
tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs

@@ -47,7 +47,64 @@ namespace Avalonia.IntegrationTests.Appium
         }
 
         [PlatformFact(TestPlatforms.Windows)]
-        public void Can_Change_Selection_With_Keyboard()
+        public void Can_Change_Selection_With_Keyboard_When_Closed()
+        {
+            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
+            var wrap = _session.FindElementByAccessibilityId("ComboBoxWrapSelection");
+
+            if (wrap.GetIsChecked() != false)
+                wrap.Click();
+
+            _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
+
+            comboBox.SendKeys(Keys.ArrowDown);
+            Assert.Equal("Item 0", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowDown);
+            Assert.Equal("Item 1", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowDown);
+            Assert.Equal("Item 1", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowUp);
+            Assert.Equal("Item 0", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowUp);
+            Assert.Equal("Item 0", comboBox.GetComboBoxValue());
+        }
+
+        [PlatformFact(TestPlatforms.Windows)]
+        public void Can_Change_Wrapping_Selection_With_Keyboard_When_Closed()
+        {
+            var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
+            var wrap = _session.FindElementByAccessibilityId("ComboBoxWrapSelection");
+
+            if (wrap.GetIsChecked() != true)
+                wrap.Click();
+
+            _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click();
+
+            comboBox.SendKeys(Keys.ArrowDown);
+            Assert.Equal("Item 0", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowDown);
+            Assert.Equal("Item 1", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowDown);
+            Assert.Equal("Item 0", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowDown);
+            Assert.Equal("Item 1", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowUp);
+            Assert.Equal("Item 0", comboBox.GetComboBoxValue());
+
+            comboBox.SendKeys(Keys.ArrowUp);
+            Assert.Equal("Item 1", comboBox.GetComboBoxValue());
+        }
+
+        [PlatformFact(TestPlatforms.Windows)]
+        public void Can_Change_Selection_When_Open_With_Keyboard()
         {
             var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");
 
@@ -64,7 +121,7 @@ namespace Avalonia.IntegrationTests.Appium
         }
 
         [PlatformFact(TestPlatforms.Windows)]
-        public void Can_Change_Selection_With_Keyboard_From_Unselected()
+        public void Can_Change_Selection_When_Open_With_Keyboard_From_Unselected()
         {
             var comboBox = _session.FindElementByAccessibilityId("BasicComboBox");