Browse Source

Merge pull request #4685 from Kir-Antipov/enhanced-autocomplete

Enhanced AutoCompleteBox
pull/4707/head
Max Katz 6 years ago
committed by GitHub
parent
commit
b793c2318f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  2. 58
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  3. 94
      src/Avalonia.Controls/AutoCompleteBox.cs
  4. 34
      tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs

5
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@ -51,6 +51,11 @@
Width="200"
Margin="0,0,0,8"
FilterMode="None"/>
<TextBlock Text="Custom Autocomplete"/>
<AutoCompleteBox Name="CustomAutocompleteBox"
Width="200"
Margin="0,0,0,8"
FilterMode="None"/>
</StackPanel>
</StackPanel>
</StackPanel>

58
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -92,13 +92,28 @@ namespace ControlCatalog.Pages
}
public StateData[] States { get; private set; }
private LinkedList<string>[] 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<string>(x.Split(' ')))
.ToArray();
}
public LinkedList<string>[] 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<AutoCompleteBox>("AsyncBox");
asyncBox.AsyncPopulator = PopulateAsync;
var customAutocompleteBox = this.FindControl<AutoCompleteBox>("CustomAutocompleteBox");
customAutocompleteBox.Items = Sentences.SelectMany(x => x);
customAutocompleteBox.TextFilter = LastWordContains;
customAutocompleteBox.TextSelector = AppendWord;
}
private IEnumerable<AutoCompleteBox> 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);

94
src/Avalonia.Controls/AutoCompleteBox.cs

@ -227,6 +227,27 @@ namespace Avalonia.Controls
Custom = 13,
}
/// <summary>
/// Represents the selector used by the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to
/// determine how the specified text should be modified with an item.
/// </summary>
/// <returns>
/// Modified text that will be used by the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.
/// </returns>
/// <param name="search">The string used as the basis for filtering.</param>
/// <param name="item">
/// The selected item that should be combined with the
/// <paramref name="search" /> parameter.
/// </param>
/// <typeparam name="T">
/// The type used for filtering the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.
/// This type can be either a string or an object.
/// </typeparam>
public delegate string AutoCompleteSelector<T>(string search, T item);
/// <summary>
/// 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
@ -364,6 +385,9 @@ namespace Avalonia.Controls
private AutoCompleteFilterPredicate<object> _itemFilter;
private AutoCompleteFilterPredicate<string> _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith);
private AutoCompleteSelector<object> _itemSelector;
private AutoCompleteSelector<string> _textSelector;
public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
@ -530,6 +554,34 @@ namespace Avalonia.Controls
(o, v) => o.TextFilter = v,
unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemSelector" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemSelector" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<object>> ItemSelectorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<object>>(
nameof(ItemSelector),
o => o.ItemSelector,
(o, v) => o.ItemSelector = v);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextSelector" />
/// dependency property.
/// </summary>
/// <value>The identifier for the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextSelector" />
/// dependency property.</value>
public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<string>> TextSelectorProperty =
AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<string>>(
nameof(TextSelector),
o => o.TextSelector,
(o, v) => o.TextSelector = v);
/// <summary>
/// Identifies the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
@ -1063,6 +1115,40 @@ namespace Avalonia.Controls
set { SetAndRaise(TextFilterProperty, ref _textFilter, value); }
}
/// <summary>
/// Gets or sets the custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />.
/// </summary>
/// <value>
/// The custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />.
/// </value>
public AutoCompleteSelector<object> ItemSelector
{
get { return _itemSelector; }
set { SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); }
}
/// <summary>
/// Gets or sets the custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// in a text-based way.
/// </summary>
/// <value>
/// The custom method that combines the user-entered
/// text and one of the items specified by the
/// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
/// in a text-based way.
/// </value>
public AutoCompleteSelector<string> TextSelector
{
get { return _textSelector; }
set { SetAndRaise(TextSelectorProperty, ref _textSelector, value); }
}
public Func<string, CancellationToken, Task<IEnumerable<object>>> AsyncPopulator
{
get { return _asyncPopulator; }
@ -2331,6 +2417,14 @@ namespace Avalonia.Controls
{
text = SearchText;
}
else if (TextSelector != null)
{
text = TextSelector(SearchText, FormatValue(newItem, true));
}
else if (ItemSelector != null)
{
text = ItemSelector(SearchText, newItem);
}
else
{
text = FormatValue(newItem, true);

34
tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs

@ -363,6 +363,40 @@ namespace Avalonia.Controls.UnitTests
});
}
[Fact]
public void Custom_TextSelector()
{
RunTest((control, textbox) =>
{
object selectedItem = control.Items.Cast<object>().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()));
});
}
[Fact]
public void Custom_ItemSelector()
{
RunTest((control, textbox) =>
{
object selectedItem = control.Items.Cast<object>().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));
});
}
/// <summary>
/// Retrieves a defined predicate filter through a new AutoCompleteBox
/// control instance.

Loading…
Cancel
Save