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.