From 5bf12ac0f0be4d1624ccf22776f5ad7a7e8cf82d Mon Sep 17 00:00:00 2001 From: Kir-Antipov Date: Wed, 16 Sep 2020 12:58:05 +0300 Subject: [PATCH 1/7] Added TextSelector and AutoCompleteMode properties to the AutoCompleteBox Now it's possible to change the logic for modifying the text of the control after choosing the autocomplete option --- src/Avalonia.Controls/AutoCompleteBox.cs | 222 ++++++++++++++++++++++- 1 file changed, 221 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index c164f282e8..16b7d09e8a 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -225,6 +225,52 @@ namespace Avalonia.Controls Custom = 13, } + /// + /// Represents the selector used by the + /// control to + /// determine how the specified text should be modified with an item. + /// + /// + /// Modified text that will be used by the + /// . + /// + /// The string used as the basis for filtering. + /// + /// The selected item that should be combined with the + /// parameter. + /// + /// + /// The type used for filtering the + /// . + /// At the moment this type known only as a string. + /// + public delegate string AutoCompleteSelector(string search, T item); + + /// + /// Specifies how the selected autocomplete result should be treated. + /// + public enum AutoCompleteMode + { + /// + /// Specifies that the text will be replaced + /// with the selected autocomplete result. + /// + Replace = 0, + + /// + /// Specifies that the selected autocomplete result + /// will be appended to the text. + /// + Append = 1, + + /// + /// Specifies that a custom selector is used. This mode is used when + /// the + /// property is set. + /// + Custom = 2 + } + /// /// Represents a control that provides a text box for user input and a /// drop-down that contains possible matches based on the input in the text @@ -362,6 +408,8 @@ namespace Avalonia.Controls private AutoCompleteFilterPredicate _itemFilter; private AutoCompleteFilterPredicate _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith); + private AutoCompleteSelector _textSelector = AutoCompleteSelection.GetSelector(AutoCompleteMode.Replace); + public static readonly RoutedEvent SelectionChangedEvent = RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox)); @@ -499,6 +547,17 @@ namespace Avalonia.Controls defaultValue: AutoCompleteFilterMode.StartsWith, validate: IsValidFilterMode); + /// + /// Gets the identifier for the + /// + /// dependency property. + /// + public static readonly StyledProperty AutoCompleteModeProperty = + AvaloniaProperty.Register( + nameof(AutoCompleteMode), + defaultValue: AutoCompleteMode.Replace, + validate: IsValidAutoCompleteMode); + /// /// Identifies the /// @@ -528,6 +587,21 @@ namespace Avalonia.Controls (o, v) => o.TextFilter = v, unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith)); + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DirectProperty> TextSelectorProperty = + AvaloniaProperty.RegisterDirect>( + nameof(TextSelector), + o => o.TextSelector, + (o, v) => o.TextSelector = v, + unsetValue: AutoCompleteSelection.GetSelector(AutoCompleteMode.Replace)); + /// /// Identifies the /// @@ -578,6 +652,19 @@ namespace Avalonia.Controls } } + private static bool IsValidAutoCompleteMode(AutoCompleteMode mode) + { + switch (mode) + { + case AutoCompleteMode.Replace: + case AutoCompleteMode.Append: + case AutoCompleteMode.Custom: + return true; + default: + return false; + } + } + /// /// Handle the change of the IsEnabled property. /// @@ -728,6 +815,19 @@ namespace Avalonia.Controls TextFilter = AutoCompleteSearch.GetFilter(mode); } + /// + /// AutoCompleteModeProperty property changed handler. + /// + /// Event arguments. + private void OnAutoCompleteModePropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + AutoCompleteMode mode = (AutoCompleteMode)e.NewValue; + + // Sets the text selector for the new value + if (mode != AutoCompleteMode.Custom) + TextSelector = AutoCompleteSelection.GetSelector(mode); + } + /// /// ItemFilterProperty property changed handler. /// @@ -748,6 +848,25 @@ namespace Avalonia.Controls } } + /// + /// TextSelectorProperty property changed handler. + /// + /// Event arguments. + private void OnTextSelectorPropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + AutoCompleteSelector value = e.NewValue as AutoCompleteSelector; + + // If null, revert to the "Replace" predicate + if (value == null) + { + AutoCompleteMode = AutoCompleteMode.Replace; + } + else if (value.Method.DeclaringType != typeof(AutoCompleteSelection)) + { + AutoCompleteMode = AutoCompleteMode.Custom; + } + } + /// /// ItemsSourceProperty property changed handler. /// @@ -793,6 +912,8 @@ namespace Avalonia.Controls SearchTextProperty.Changed.AddClassHandler((x,e) => x.OnSearchTextPropertyChanged(e)); FilterModeProperty.Changed.AddClassHandler((x,e) => x.OnFilterModePropertyChanged(e)); ItemFilterProperty.Changed.AddClassHandler((x,e) => x.OnItemFilterPropertyChanged(e)); + AutoCompleteModeProperty.Changed.AddClassHandler((x,e) => x.OnAutoCompleteModePropertyChanged(e)); + TextSelectorProperty.Changed.AddClassHandler((x,e) => x.OnTextSelectorPropertyChanged(e)); ItemsProperty.Changed.AddClassHandler((x,e) => x.OnItemsPropertyChanged(e)); IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnControlIsEnabledChanged(e)); } @@ -1015,6 +1136,31 @@ namespace Avalonia.Controls set { SetValue(FilterModeProperty, value); } } + /// + /// Gets or sets how the text in the text box will be modified + /// with the selected autocomplete item. + /// + /// + /// One of the + /// values. The default is + /// . + /// + /// The specified value is not a valid + /// . + /// + /// + /// Use the AutoCompleteMode property to specify the way the text will + /// be modified with the selected autocomplete item. For example, text + /// can be modified in a predefined or custom way. The autocomplete + /// mode is automatically set to Custom if you set the TextSelector + /// property. + /// + public AutoCompleteMode AutoCompleteMode + { + get { return GetValue(AutoCompleteModeProperty); } + set { SetValue(AutoCompleteModeProperty, value); } + } + public string Watermark { get { return GetValue(WatermarkProperty); } @@ -1061,6 +1207,26 @@ namespace Avalonia.Controls set { SetAndRaise(TextFilterProperty, ref _textFilter, value); } } + /// + /// Gets or sets the custom method that combines the user-entered + /// text to and one of the items specified by the + /// . + /// + /// + /// The custom method that combines the user-entered + /// text to and one of the items specified by the + /// . + /// + /// + /// The AutoCompleteMode is automatically set to Custom if you set + /// the TextSelector property. + /// + public AutoCompleteSelector TextSelector + { + get { return _textSelector; } + set { SetAndRaise(TextSelectorProperty, ref _textSelector, value); } + } + public Func>> AsyncPopulator { get { return _asyncPopulator; } @@ -2331,7 +2497,7 @@ namespace Avalonia.Controls } else { - text = FormatValue(newItem, true); + text = TextSelector(SearchText, FormatValue(newItem, true)); } // Update the Text property and the TextBox values @@ -2590,6 +2756,60 @@ namespace Avalonia.Controls } } + /// + /// A predefined set of selector functions for the known, built-in + /// AutoCompleteMode enumeration values. + /// + private static class AutoCompleteSelection + { + /// + /// Index function that retrieves the selector for the provided + /// AutoCompleteMode. + /// + /// The built-in autocomplete mode. + /// Returns the string-based selector function. + public static AutoCompleteSelector GetSelector(AutoCompleteMode completeMode) + { + switch (completeMode) + { + case AutoCompleteMode.Replace: + return Replace; + case AutoCompleteMode.Append: + return Append; + case AutoCompleteMode.Custom: + default: + return null; + } + } + + /// + /// Implements AutoCompleteMode.Replace. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// + /// Return the and ignores the + /// . + /// + private static string Replace(string text, string value) + { + return value ?? String.Empty; + } + + /// + /// Implements AutoCompleteMode.Append. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// + /// Returns the concatenated string. + /// + private static string Append(string text, string value) + { + return text + value; + } + } + /// /// A framework element that permits a binding to be evaluated in a new data /// context leaf node. From 9a1dd58273ff33d3c53cfb9d9f288d21e8e6bf38 Mon Sep 17 00:00:00 2001 From: Kir-Antipov Date: Wed, 16 Sep 2020 12:59:58 +0300 Subject: [PATCH 2/7] Well, default AutoCompleteMode.Append isn't doing well, tbh --- src/Avalonia.Controls/AutoCompleteBox.cs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 16b7d09e8a..22b09ef110 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -257,18 +257,12 @@ namespace Avalonia.Controls /// Replace = 0, - /// - /// Specifies that the selected autocomplete result - /// will be appended to the text. - /// - Append = 1, - /// /// Specifies that a custom selector is used. This mode is used when /// the /// property is set. /// - Custom = 2 + Custom = 1 } /// @@ -657,7 +651,6 @@ namespace Avalonia.Controls switch (mode) { case AutoCompleteMode.Replace: - case AutoCompleteMode.Append: case AutoCompleteMode.Custom: return true; default: @@ -2774,8 +2767,6 @@ namespace Avalonia.Controls { case AutoCompleteMode.Replace: return Replace; - case AutoCompleteMode.Append: - return Append; case AutoCompleteMode.Custom: default: return null; @@ -2795,19 +2786,6 @@ namespace Avalonia.Controls { return value ?? String.Empty; } - - /// - /// Implements AutoCompleteMode.Append. - /// - /// The AutoCompleteBox prefix text. - /// The item's string value. - /// - /// Returns the concatenated string. - /// - private static string Append(string text, string value) - { - return text + value; - } } /// From 0fa3350a9d704d8d94b6635b0db80d59c9518d87 Mon Sep 17 00:00:00 2001 From: Kir-Antipov Date: Wed, 16 Sep 2020 13:25:38 +0300 Subject: [PATCH 3/7] Made tests for the AutoCompleteMode and TextSelector properties --- .../AutoCompleteBoxTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index 57cea91834..2205384542 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -363,6 +363,47 @@ namespace Avalonia.Controls.UnitTests }); } + [Fact] + public void Test_Selectors() + { + Assert.Equal(GetSelector(AutoCompleteMode.Replace)("Never", "gonna"), "gonna"); + Assert.Equal(GetSelector(AutoCompleteMode.Replace)("give", "you"), "you"); + Assert.NotEqual(GetSelector(AutoCompleteMode.Replace)("up", "!"), "42"); + } + + [Fact] + public void AutoCompleteMode_Changes_To_Custom_And_Back() + { + RunTest((control, textbox) => + { + Assert.Equal(control.AutoCompleteMode, AutoCompleteMode.Replace); + + control.TextSelector = (text, item) => text + item; + Assert.Equal(control.AutoCompleteMode, AutoCompleteMode.Custom); + + control.AutoCompleteMode = AutoCompleteMode.Replace; + Assert.Equal(control.AutoCompleteMode, AutoCompleteMode.Replace); + Assert.Equal(control.TextSelector, GetSelector(AutoCompleteMode.Replace)); + }); + } + + [Fact] + public void Custom_TextSelector() + { + RunTest((control, textbox) => + { + object selectedItem = control.Items.Cast().First(); + string input = "42"; + + control.TextSelector = (text, item) => text + item; + Assert.Equal(control.TextSelector("4", "2"), "42"); + + control.Text = input; + control.SelectedItem = selectedItem; + Assert.Equal(control.Text, control.TextSelector(input, selectedItem.ToString())); + }); + } + /// /// Retrieves a defined predicate filter through a new AutoCompleteBox /// control instance. @@ -375,6 +416,17 @@ namespace Avalonia.Controls.UnitTests .TextFilter; } + /// + /// Retrieves a defined selector through a new AutoCompleteBox + /// control instance. + /// + /// The AutoCompleteMode of interest. + /// Returns the selector instance. + private static AutoCompleteSelector GetSelector(AutoCompleteMode mode) + { + return new AutoCompleteBox { AutoCompleteMode = mode }.TextSelector; + } + /// /// Creates a large list of strings for AutoCompleteBox testing. /// From 7481aea60620b05a915fdb03b20ee56dc731908c Mon Sep 17 00:00:00 2001 From: Kir-Antipov Date: Mon, 21 Sep 2020 00:57:08 +0300 Subject: [PATCH 4/7] Removed AutoCompleteMode enum --- src/Avalonia.Controls/AutoCompleteBox.cs | 148 +----------------- .../AutoCompleteBoxTests.cs | 35 ----- 2 files changed, 4 insertions(+), 179 deletions(-) diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 22b09ef110..c9b50a46fd 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -246,25 +246,6 @@ namespace Avalonia.Controls /// public delegate string AutoCompleteSelector(string search, T item); - /// - /// Specifies how the selected autocomplete result should be treated. - /// - public enum AutoCompleteMode - { - /// - /// Specifies that the text will be replaced - /// with the selected autocomplete result. - /// - Replace = 0, - - /// - /// Specifies that a custom selector is used. This mode is used when - /// the - /// property is set. - /// - Custom = 1 - } - /// /// Represents a control that provides a text box for user input and a /// drop-down that contains possible matches based on the input in the text @@ -402,7 +383,7 @@ namespace Avalonia.Controls private AutoCompleteFilterPredicate _itemFilter; private AutoCompleteFilterPredicate _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith); - private AutoCompleteSelector _textSelector = AutoCompleteSelection.GetSelector(AutoCompleteMode.Replace); + private AutoCompleteSelector _textSelector; public static readonly RoutedEvent SelectionChangedEvent = RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox)); @@ -541,17 +522,6 @@ namespace Avalonia.Controls defaultValue: AutoCompleteFilterMode.StartsWith, validate: IsValidFilterMode); - /// - /// Gets the identifier for the - /// - /// dependency property. - /// - public static readonly StyledProperty AutoCompleteModeProperty = - AvaloniaProperty.Register( - nameof(AutoCompleteMode), - defaultValue: AutoCompleteMode.Replace, - validate: IsValidAutoCompleteMode); - /// /// Identifies the /// @@ -593,8 +563,7 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect>( nameof(TextSelector), o => o.TextSelector, - (o, v) => o.TextSelector = v, - unsetValue: AutoCompleteSelection.GetSelector(AutoCompleteMode.Replace)); + (o, v) => o.TextSelector = v); /// /// Identifies the @@ -646,18 +615,6 @@ namespace Avalonia.Controls } } - private static bool IsValidAutoCompleteMode(AutoCompleteMode mode) - { - switch (mode) - { - case AutoCompleteMode.Replace: - case AutoCompleteMode.Custom: - return true; - default: - return false; - } - } - /// /// Handle the change of the IsEnabled property. /// @@ -808,19 +765,6 @@ namespace Avalonia.Controls TextFilter = AutoCompleteSearch.GetFilter(mode); } - /// - /// AutoCompleteModeProperty property changed handler. - /// - /// Event arguments. - private void OnAutoCompleteModePropertyChanged(AvaloniaPropertyChangedEventArgs e) - { - AutoCompleteMode mode = (AutoCompleteMode)e.NewValue; - - // Sets the text selector for the new value - if (mode != AutoCompleteMode.Custom) - TextSelector = AutoCompleteSelection.GetSelector(mode); - } - /// /// ItemFilterProperty property changed handler. /// @@ -841,25 +785,6 @@ namespace Avalonia.Controls } } - /// - /// TextSelectorProperty property changed handler. - /// - /// Event arguments. - private void OnTextSelectorPropertyChanged(AvaloniaPropertyChangedEventArgs e) - { - AutoCompleteSelector value = e.NewValue as AutoCompleteSelector; - - // If null, revert to the "Replace" predicate - if (value == null) - { - AutoCompleteMode = AutoCompleteMode.Replace; - } - else if (value.Method.DeclaringType != typeof(AutoCompleteSelection)) - { - AutoCompleteMode = AutoCompleteMode.Custom; - } - } - /// /// ItemsSourceProperty property changed handler. /// @@ -905,8 +830,6 @@ namespace Avalonia.Controls SearchTextProperty.Changed.AddClassHandler((x,e) => x.OnSearchTextPropertyChanged(e)); FilterModeProperty.Changed.AddClassHandler((x,e) => x.OnFilterModePropertyChanged(e)); ItemFilterProperty.Changed.AddClassHandler((x,e) => x.OnItemFilterPropertyChanged(e)); - AutoCompleteModeProperty.Changed.AddClassHandler((x,e) => x.OnAutoCompleteModePropertyChanged(e)); - TextSelectorProperty.Changed.AddClassHandler((x,e) => x.OnTextSelectorPropertyChanged(e)); ItemsProperty.Changed.AddClassHandler((x,e) => x.OnItemsPropertyChanged(e)); IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnControlIsEnabledChanged(e)); } @@ -1129,31 +1052,6 @@ namespace Avalonia.Controls set { SetValue(FilterModeProperty, value); } } - /// - /// Gets or sets how the text in the text box will be modified - /// with the selected autocomplete item. - /// - /// - /// One of the - /// values. The default is - /// . - /// - /// The specified value is not a valid - /// . - /// - /// - /// Use the AutoCompleteMode property to specify the way the text will - /// be modified with the selected autocomplete item. For example, text - /// can be modified in a predefined or custom way. The autocomplete - /// mode is automatically set to Custom if you set the TextSelector - /// property. - /// - public AutoCompleteMode AutoCompleteMode - { - get { return GetValue(AutoCompleteModeProperty); } - set { SetValue(AutoCompleteModeProperty, value); } - } - public string Watermark { get { return GetValue(WatermarkProperty); } @@ -2490,7 +2388,8 @@ namespace Avalonia.Controls } else { - text = TextSelector(SearchText, FormatValue(newItem, true)); + string formattedValue = FormatValue(newItem, true); + text = TextSelector == null ? formattedValue : TextSelector(SearchText, formattedValue); } // Update the Text property and the TextBox values @@ -2749,45 +2648,6 @@ namespace Avalonia.Controls } } - /// - /// A predefined set of selector functions for the known, built-in - /// AutoCompleteMode enumeration values. - /// - private static class AutoCompleteSelection - { - /// - /// Index function that retrieves the selector for the provided - /// AutoCompleteMode. - /// - /// The built-in autocomplete mode. - /// Returns the string-based selector function. - public static AutoCompleteSelector GetSelector(AutoCompleteMode completeMode) - { - switch (completeMode) - { - case AutoCompleteMode.Replace: - return Replace; - case AutoCompleteMode.Custom: - default: - return null; - } - } - - /// - /// Implements AutoCompleteMode.Replace. - /// - /// The AutoCompleteBox prefix text. - /// The item's string value. - /// - /// Return the and ignores the - /// . - /// - private static string Replace(string text, string value) - { - return value ?? String.Empty; - } - } - /// /// A framework element that permits a binding to be evaluated in a new data /// context leaf node. diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index 2205384542..2e609132d6 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -363,30 +363,6 @@ namespace Avalonia.Controls.UnitTests }); } - [Fact] - public void Test_Selectors() - { - Assert.Equal(GetSelector(AutoCompleteMode.Replace)("Never", "gonna"), "gonna"); - Assert.Equal(GetSelector(AutoCompleteMode.Replace)("give", "you"), "you"); - Assert.NotEqual(GetSelector(AutoCompleteMode.Replace)("up", "!"), "42"); - } - - [Fact] - public void AutoCompleteMode_Changes_To_Custom_And_Back() - { - RunTest((control, textbox) => - { - Assert.Equal(control.AutoCompleteMode, AutoCompleteMode.Replace); - - control.TextSelector = (text, item) => text + item; - Assert.Equal(control.AutoCompleteMode, AutoCompleteMode.Custom); - - control.AutoCompleteMode = AutoCompleteMode.Replace; - Assert.Equal(control.AutoCompleteMode, AutoCompleteMode.Replace); - Assert.Equal(control.TextSelector, GetSelector(AutoCompleteMode.Replace)); - }); - } - [Fact] public void Custom_TextSelector() { @@ -416,17 +392,6 @@ namespace Avalonia.Controls.UnitTests .TextFilter; } - /// - /// Retrieves a defined selector through a new AutoCompleteBox - /// control instance. - /// - /// The AutoCompleteMode of interest. - /// Returns the selector instance. - private static AutoCompleteSelector GetSelector(AutoCompleteMode mode) - { - return new AutoCompleteBox { AutoCompleteMode = mode }.TextSelector; - } - /// /// Creates a large list of strings for AutoCompleteBox testing. /// From bc87efc65fbae7ab338716f1e89a43b48fea6428 Mon Sep 17 00:00:00 2001 From: Kir-Antipov Date: Mon, 21 Sep 2020 01:56:05 +0300 Subject: [PATCH 5/7] Added ItemSelector property to the AutoCompleteBox --- src/Avalonia.Controls/AutoCompleteBox.cs | 54 ++++++++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index c9b50a46fd..c119fd1964 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -242,7 +242,7 @@ namespace Avalonia.Controls /// /// The type used for filtering the /// . - /// At the moment this type known only as a string. + /// This type can be either a string or an object. /// public delegate string AutoCompleteSelector(string search, T item); @@ -383,6 +383,7 @@ namespace Avalonia.Controls private AutoCompleteFilterPredicate _itemFilter; private AutoCompleteFilterPredicate _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith); + private AutoCompleteSelector _itemSelector; private AutoCompleteSelector _textSelector; public static readonly RoutedEvent SelectionChangedEvent = @@ -551,6 +552,20 @@ namespace Avalonia.Controls (o, v) => o.TextFilter = v, unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith)); + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DirectProperty> ItemSelectorProperty = + AvaloniaProperty.RegisterDirect>( + nameof(ItemSelector), + o => o.ItemSelector, + (o, v) => o.ItemSelector = v); + /// /// Identifies the /// @@ -1100,18 +1115,32 @@ namespace Avalonia.Controls /// /// Gets or sets the custom method that combines the user-entered - /// text to and one of the items specified by the + /// text and one of the items specified by the /// . /// /// /// The custom method that combines the user-entered - /// text to and one of the items specified by the + /// text and one of the items specified by the /// . /// - /// - /// The AutoCompleteMode is automatically set to Custom if you set - /// the TextSelector property. - /// + public AutoCompleteSelector ItemSelector + { + get { return _itemSelector; } + set { SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); } + } + + /// + /// Gets or sets the custom method that combines the user-entered + /// text and one of the items specified by the + /// + /// in a text-based way. + /// + /// + /// The custom method that combines the user-entered + /// text and one of the items specified by the + /// + /// in a text-based way. + /// public AutoCompleteSelector TextSelector { get { return _textSelector; } @@ -2386,10 +2415,17 @@ namespace Avalonia.Controls { text = SearchText; } + else if (TextSelector != null) + { + text = TextSelector(SearchText, FormatValue(newItem, true)); + } + else if (ItemSelector != null) + { + text = ItemSelector(SearchText, newItem); + } else { - string formattedValue = FormatValue(newItem, true); - text = TextSelector == null ? formattedValue : TextSelector(SearchText, formattedValue); + text = FormatValue(newItem, true); } // Update the Text property and the TextBox values From 997385eab7307201943dc94b88d417b5e3207fbd Mon Sep 17 00:00:00 2001 From: Kir-Antipov Date: Mon, 21 Sep 2020 01:56:34 +0300 Subject: [PATCH 6/7] Made test for AutoCompleteBox.ItemSelector --- .../AutoCompleteBoxTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index 2e609132d6..3e78e951e2 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -379,6 +379,23 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(control.Text, control.TextSelector(input, selectedItem.ToString())); }); } + + [Fact] + public void Custom_ItemSelector() + { + RunTest((control, textbox) => + { + object selectedItem = control.Items.Cast().First(); + string input = "42"; + + control.ItemSelector = (text, item) => text + item; + Assert.Equal(control.ItemSelector("4", 2), "42"); + + control.Text = input; + control.SelectedItem = selectedItem; + Assert.Equal(control.Text, control.ItemSelector(input, selectedItem)); + }); + } /// /// Retrieves a defined predicate filter through a new AutoCompleteBox From 3f3ec4b8351a1f034f71584751ca71ba03fe089b Mon Sep 17 00:00:00 2001 From: Kir-Antipov Date: Tue, 22 Sep 2020 03:40:10 +0300 Subject: [PATCH 7/7] Added custom TextSelector example --- .../Pages/AutoCompleteBoxPage.xaml | 5 ++ .../Pages/AutoCompleteBoxPage.xaml.cs | 58 ++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml index f90a0c4658..a49616e543 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml @@ -51,6 +51,11 @@ Width="200" Margin="0,0,0,8" FilterMode="None"/> + + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index f9d6a72a3a..574cc79a7d 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -92,13 +92,28 @@ namespace ControlCatalog.Pages } public StateData[] States { get; private set; } + private LinkedList[] BuildAllSentences() + { + return new string[] + { + "Hello world", + "No this is Patrick", + "Never gonna give you up", + "How does one patch KDE2 under FreeBSD" + } + .Select(x => new LinkedList(x.Split(' '))) + .ToArray(); + } + public LinkedList[] Sentences { get; private set; } + public AutoCompleteBoxPage() { this.InitializeComponent(); States = BuildAllStates(); + Sentences = BuildAllSentences(); - foreach (AutoCompleteBox box in GetAllAutoCompleteBox()) + foreach (AutoCompleteBox box in GetAllAutoCompleteBox().Where(x => x.Name != "CustomAutocompleteBox")) { box.Items = States; } @@ -116,6 +131,11 @@ namespace ControlCatalog.Pages var asyncBox = this.FindControl("AsyncBox"); asyncBox.AsyncPopulator = PopulateAsync; + + var customAutocompleteBox = this.FindControl("CustomAutocompleteBox"); + customAutocompleteBox.Items = Sentences.SelectMany(x => x); + customAutocompleteBox.TextFilter = LastWordContains; + customAutocompleteBox.TextSelector = AppendWord; } private IEnumerable GetAllAutoCompleteBox() { @@ -137,6 +157,42 @@ namespace ControlCatalog.Pages .ToList(); } + private bool LastWordContains(string searchText, string item) + { + var words = searchText.Split(' '); + var options = Sentences.Select(x => x.First).ToArray(); + for (var i = 0; i < words.Length; ++i) + { + var word = words[i]; + for (var j = 0; j < options.Length; ++j) + { + var option = options[j]; + if (option == null) + continue; + + if (i == words.Length - 1) + { + options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null; + } + else + { + options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; + } + } + } + + return options.Any(x => x != null && x.Value == item); + } + private string AppendWord(string text, string item) + { + string[] parts = text.Split(' '); + if (parts.Length == 0) + return item; + + parts[parts.Length - 1] = item; + return string.Join(" ", parts); + } + private void InitializeComponent() { AvaloniaXamlLoader.Load(this);