From e4cf7a6bf75c087769e738bb14047072a2c28a53 Mon Sep 17 00:00:00 2001 From: luthfiampas Date: Thu, 4 Feb 2021 01:52:45 +0700 Subject: [PATCH] add auto-select functionality to ComboBox control --- src/Avalonia.Controls/ComboBox.cs | 72 +++++++++++++++++++ .../ComboBoxTests.cs | 37 ++++++++++ 2 files changed, 109 insertions(+) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 7f2acb58fe..95baf8146d 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -10,6 +10,7 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -76,6 +77,14 @@ namespace Avalonia.Controls public static readonly StyledProperty VerticalContentAlignmentProperty = ContentControl.VerticalContentAlignmentProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsAutoSelectEnabledProperty = + AvaloniaProperty.Register(nameof(IsAutoSelectEnabled)); + + private string _autoSelectTerm = string.Empty; + private DispatcherTimer _autoSelectTimer; private bool _isDropDownOpen; private Popup _popup; private object _selectionBoxItem; @@ -164,6 +173,17 @@ namespace Avalonia.Controls set { SetValue(VerticalContentAlignmentProperty, value); } } + /// + /// Gets or sets value indicating whether auto-select is currently enabled. + /// If true, the control will try to find then select matched + /// based on current keyboard inputs. + /// + public bool IsAutoSelectEnabled + { + get { return GetValue(IsAutoSelectEnabledProperty); } + set { SetValue(IsAutoSelectEnabledProperty, value); } + } + /// protected override IItemContainerGenerator CreateItemContainerGenerator() { @@ -229,6 +249,32 @@ namespace Avalonia.Controls } } + /// + protected override void OnTextInput(TextInputEventArgs e) + { + if (!IsAutoSelectEnabled || e.Handled) + return; + + StopAutoSelectTimer(); + + _autoSelectTerm += e.Text; + + bool match(ItemContainerInfo info) => + info.ContainerControl is IContentControl control && + control.Content?.ToString()?.StartsWith(_autoSelectTerm, StringComparison.OrdinalIgnoreCase) == true; + + var info = ItemContainerGenerator.Containers.FirstOrDefault(match); + + if (info != null) + { + SelectedIndex = info.Index; + } + + StartAutoSelectTimer(); + + e.Handled = true; + } + /// protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { @@ -426,5 +472,31 @@ namespace Avalonia.Controls SelectedIndex = prev; } + + private void StartAutoSelectTimer() + { + _autoSelectTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; + _autoSelectTimer.Tick += AutoSelectTimer_Tick; + _autoSelectTimer.Start(); + } + + private void StopAutoSelectTimer() + { + if (_autoSelectTimer == null) + { + return; + } + + _autoSelectTimer.Stop(); + _autoSelectTimer.Tick -= AutoSelectTimer_Tick; + + _autoSelectTimer = null; + } + + private void AutoSelectTimer_Tick(object sender, EventArgs e) + { + _autoSelectTerm = string.Empty; + StopAutoSelectTimer(); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index c8a30a42e9..218832d988 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -1,7 +1,9 @@ +using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.UnitTests; @@ -137,5 +139,40 @@ namespace Avalonia.Controls.UnitTests Assert.True(other.IsFocused); } } + + [Theory] + [InlineData(-1, 2, "c", "A item", "B item", "C item")] + [InlineData(0, 1, "b", "A item", "B item", "C item")] + [InlineData(2, 2, "x", "A item", "B item", "C item")] + public void AutoSelect_Should_Have_Expected_SelectedIndex( + int initialSelectedIndex, + int expectedSelectedIndex, + string searchTerm, + params string[] items) + { + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + var target = new ComboBox + { + IsAutoSelectEnabled = true, + Template = GetTemplate(), + Items = items.Select(x => new ComboBoxItem { Content = x }) + }; + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedIndex = initialSelectedIndex; + + var args = new TextInputEventArgs + { + Text = searchTerm, + RoutedEvent = InputElement.TextInputEvent + }; + + target.RaiseEvent(args); + + Assert.Equal(expectedSelectedIndex, target.SelectedIndex); + } + } } }