|
|
@@ -0,0 +1,2726 @@
|
|
|
+// (c) Copyright Microsoft Corporation.
|
|
|
+// This source is subject to the Microsoft Public License (Ms-PL).
|
|
|
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
|
|
|
+// All other rights reserved.
|
|
|
+
|
|
|
+using System;
|
|
|
+using System.Collections;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Collections.ObjectModel;
|
|
|
+using System.Collections.Specialized;
|
|
|
+using System.ComponentModel;
|
|
|
+using System.Linq;
|
|
|
+using System.Threading;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using Avalonia.Collections;
|
|
|
+using Avalonia.Controls.Primitives;
|
|
|
+using Avalonia.Controls.Templates;
|
|
|
+using Avalonia.Controls.Utils;
|
|
|
+using Avalonia.Data;
|
|
|
+using Avalonia.Input;
|
|
|
+using Avalonia.Interactivity;
|
|
|
+using Avalonia.Threading;
|
|
|
+using Avalonia.VisualTree;
|
|
|
+
|
|
|
+namespace Avalonia.Controls
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// Provides data for the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ public class PopulatedEventArgs : EventArgs
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the list of possible matches added to the drop-down portion of
|
|
|
+ /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
|
|
|
+ /// control.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The list of possible matches added to the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
|
|
|
+ public IEnumerable Data { get; private set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="data">The list of possible matches added to the
|
|
|
+ /// drop-down portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
|
|
|
+ public PopulatedEventArgs(IEnumerable data)
|
|
|
+ {
|
|
|
+ Data = data;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Provides data for the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <QualityBand>Stable</QualityBand>
|
|
|
+ public class PopulatingEventArgs : CancelEventArgs
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the text that is used to determine which items to display in
|
|
|
+ /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
|
|
|
+ /// control.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The text that is used to determine which items to display in
|
|
|
+ /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
|
|
|
+ public string Parameter { get; private set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.PopulatingEventArgs" />.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="parameter">The value of the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
|
|
|
+ /// property, which is used to filter items for the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
|
|
|
+ public PopulatingEventArgs(string parameter)
|
|
|
+ {
|
|
|
+ Parameter = parameter;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Represents the filter used by the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to
|
|
|
+ /// determine whether an item is a possible match for the specified text.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>true to indicate <paramref name="item" /> is a possible match
|
|
|
+ /// for <paramref name="search" />; otherwise false.</returns>
|
|
|
+ /// <param name="search">The string used as the basis for filtering.</param>
|
|
|
+ /// <param name="item">The item that is compared 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>
|
|
|
+ /// <QualityBand>Stable</QualityBand>
|
|
|
+ public delegate bool AutoCompleteFilterPredicate<T>(string search, T item);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies how text in the text box portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control is used
|
|
|
+ /// to filter items specified by the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// property for display in the drop-down.
|
|
|
+ /// </summary>
|
|
|
+ /// <QualityBand>Stable</QualityBand>
|
|
|
+ public enum AutoCompleteFilterMode
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies that no filter is used. All items are returned.
|
|
|
+ /// </summary>
|
|
|
+ None = 0,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies a culture-sensitive, case-insensitive filter where the
|
|
|
+ /// returned items start with the specified text. The filter uses the
|
|
|
+ /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
|
|
|
+ /// method, specifying
|
|
|
+ /// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
|
|
|
+ /// the string comparison criteria.
|
|
|
+ /// </summary>
|
|
|
+ StartsWith = 1,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies a culture-sensitive, case-sensitive filter where the
|
|
|
+ /// returned items start with the specified text. The filter uses the
|
|
|
+ /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
|
|
|
+ /// method, specifying
|
|
|
+ /// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
|
|
|
+ /// comparison criteria.
|
|
|
+ /// </summary>
|
|
|
+ StartsWithCaseSensitive = 2,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies an ordinal, case-insensitive filter where the returned
|
|
|
+ /// items start with the specified text. The filter uses the
|
|
|
+ /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
|
|
|
+ /// method, specifying
|
|
|
+ /// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
|
|
|
+ /// string comparison criteria.
|
|
|
+ /// </summary>
|
|
|
+ StartsWithOrdinal = 3,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies an ordinal, case-sensitive filter where the returned items
|
|
|
+ /// start with the specified text. The filter uses the
|
|
|
+ /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
|
|
|
+ /// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
|
|
|
+ /// the string comparison criteria.
|
|
|
+ /// </summary>
|
|
|
+ StartsWithOrdinalCaseSensitive = 4,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies a culture-sensitive, case-insensitive filter where the
|
|
|
+ /// returned items contain the specified text.
|
|
|
+ /// </summary>
|
|
|
+ Contains = 5,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies a culture-sensitive, case-sensitive filter where the
|
|
|
+ /// returned items contain the specified text.
|
|
|
+ /// </summary>
|
|
|
+ ContainsCaseSensitive = 6,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies an ordinal, case-insensitive filter where the returned
|
|
|
+ /// items contain the specified text.
|
|
|
+ /// </summary>
|
|
|
+ ContainsOrdinal = 7,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies an ordinal, case-sensitive filter where the returned items
|
|
|
+ /// contain the specified text.
|
|
|
+ /// </summary>
|
|
|
+ ContainsOrdinalCaseSensitive = 8,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies a culture-sensitive, case-insensitive filter where the
|
|
|
+ /// returned items equal the specified text. The filter uses the
|
|
|
+ /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
|
|
|
+ /// method, specifying
|
|
|
+ /// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
|
|
|
+ /// the search comparison criteria.
|
|
|
+ /// </summary>
|
|
|
+ Equals = 9,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies a culture-sensitive, case-sensitive filter where the
|
|
|
+ /// returned items equal the specified text. The filter uses the
|
|
|
+ /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
|
|
|
+ /// method, specifying
|
|
|
+ /// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
|
|
|
+ /// comparison criteria.
|
|
|
+ /// </summary>
|
|
|
+ EqualsCaseSensitive = 10,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies an ordinal, case-insensitive filter where the returned
|
|
|
+ /// items equal the specified text. The filter uses the
|
|
|
+ /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
|
|
|
+ /// method, specifying
|
|
|
+ /// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
|
|
|
+ /// string comparison criteria.
|
|
|
+ /// </summary>
|
|
|
+ EqualsOrdinal = 11,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies an ordinal, case-sensitive filter where the returned items
|
|
|
+ /// equal the specified text. The filter uses the
|
|
|
+ /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
|
|
|
+ /// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
|
|
|
+ /// the string comparison criteria.
|
|
|
+ /// </summary>
|
|
|
+ EqualsOrdinalCaseSensitive = 12,
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies that a custom filter is used. This mode is used when the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
|
|
|
+ /// or
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
|
|
|
+ /// properties are set.
|
|
|
+ /// </summary>
|
|
|
+ Custom = 13,
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <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
|
|
|
+ /// box.
|
|
|
+ /// </summary>
|
|
|
+ public class AutoCompleteBox : TemplatedControl
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies the name of the selection adapter TemplatePart.
|
|
|
+ /// </summary>
|
|
|
+ private const string ElementSelectionAdapter = "PART_SelectionAdapter";
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies the name of the Selector TemplatePart.
|
|
|
+ /// </summary>
|
|
|
+ private const string ElementSelector = "PART_SelectingItemsControl";
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Specifies the name of the Popup TemplatePart.
|
|
|
+ /// </summary>
|
|
|
+ private const string ElementPopup = "PART_Popup";
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// The name for the text box part.
|
|
|
+ /// </summary>
|
|
|
+ private const string ElementTextBox = "PART_TextBox";
|
|
|
+
|
|
|
+ private IEnumerable _itemsEnumerable;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a local cached copy of the items data.
|
|
|
+ /// </summary>
|
|
|
+ private List<object> _items;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the observable collection that contains references to
|
|
|
+ /// all of the items in the generated view of data that is provided to
|
|
|
+ /// the selection-style control adapter.
|
|
|
+ /// </summary>
|
|
|
+ private AvaloniaList<object> _view;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value to ignore a number of pending change handlers.
|
|
|
+ /// The value is decremented after each use. This is used to reset the
|
|
|
+ /// value of properties without performing any of the actions in their
|
|
|
+ /// change handlers.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>The int is important as a value because the TextBox
|
|
|
+ /// TextChanged event does not immediately fire, and this will allow for
|
|
|
+ /// nested property changes to be ignored.</remarks>
|
|
|
+ private int _ignoreTextPropertyChange;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether to ignore calling a pending
|
|
|
+ /// change handlers.
|
|
|
+ /// </summary>
|
|
|
+ private bool _ignorePropertyChange;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether to ignore the selection
|
|
|
+ /// changed event.
|
|
|
+ /// </summary>
|
|
|
+ private bool _ignoreTextSelectionChange;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether to skip the text update
|
|
|
+ /// processing when the selected item is updated.
|
|
|
+ /// </summary>
|
|
|
+ private bool _skipSelectedItemTextUpdate;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the last observed text box selection start location.
|
|
|
+ /// </summary>
|
|
|
+ private int _textSelectionStart;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether the user initiated the
|
|
|
+ /// current populate call.
|
|
|
+ /// </summary>
|
|
|
+ private bool _userCalledPopulate;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A value indicating whether the popup has been opened at least once.
|
|
|
+ /// </summary>
|
|
|
+ private bool _popupHasOpened;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the DispatcherTimer used for the MinimumPopulateDelay
|
|
|
+ /// condition for auto completion.
|
|
|
+ /// </summary>
|
|
|
+ private DispatcherTimer _delayTimer;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether a read-only dependency
|
|
|
+ /// property change handler should allow the value to be set. This is
|
|
|
+ /// used to ensure that read-only properties cannot be changed via
|
|
|
+ /// SetValue, etc.
|
|
|
+ /// </summary>
|
|
|
+ private bool _allowWrite;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// The TextBox template part.
|
|
|
+ /// </summary>
|
|
|
+ private TextBox _textBox;
|
|
|
+ private IDisposable _textBoxSubscriptions;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// The SelectionAdapter.
|
|
|
+ /// </summary>
|
|
|
+ private ISelectionAdapter _adapter;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A control that can provide updated string values from a binding.
|
|
|
+ /// </summary>
|
|
|
+ private BindingEvaluator<string> _valueBindingEvaluator;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A weak subscription for the collection changed event.
|
|
|
+ /// </summary>
|
|
|
+ private IDisposable _collectionChangeSubscription;
|
|
|
+
|
|
|
+ private IMemberSelector _valueMemberSelector;
|
|
|
+ private Func<string, CancellationToken, Task<IEnumerable<object>>> _asyncPopulator;
|
|
|
+ private CancellationTokenSource _populationCancellationTokenSource;
|
|
|
+
|
|
|
+ private bool _itemTemplateIsFromValueMemeberBinding = true;
|
|
|
+ private bool _settingItemTemplateFromValueMemeberBinding;
|
|
|
+
|
|
|
+ private object _selectedItem;
|
|
|
+ private bool _isDropDownOpen;
|
|
|
+ private bool _isFocused = false;
|
|
|
+
|
|
|
+ private string _text = string.Empty;
|
|
|
+ private string _searchText = string.Empty;
|
|
|
+
|
|
|
+ private AutoCompleteFilterPredicate<object> _itemFilter;
|
|
|
+ private AutoCompleteFilterPredicate<string> _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith);
|
|
|
+
|
|
|
+ public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
|
|
|
+ RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
|
|
|
+
|
|
|
+ public static readonly StyledProperty<string> WatermarkProperty =
|
|
|
+ TextBox.WatermarkProperty.AddOwner<AutoCompleteBox>();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly StyledProperty<int> MinimumPrefixLengthProperty =
|
|
|
+ AvaloniaProperty.Register<AutoCompleteBox, int>(
|
|
|
+ nameof(MinimumPrefixLength), 1,
|
|
|
+ validate: ValidateMinimumPrefixLength);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly StyledProperty<TimeSpan> MinimumPopulateDelayProperty =
|
|
|
+ AvaloniaProperty.Register<AutoCompleteBox, TimeSpan>(
|
|
|
+ nameof(MinimumPopulateDelay),
|
|
|
+ TimeSpan.Zero,
|
|
|
+ validate: ValidateMinimumPopulateDelay);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly StyledProperty<double> MaxDropDownHeightProperty =
|
|
|
+ AvaloniaProperty.Register<AutoCompleteBox, double>(
|
|
|
+ nameof(MaxDropDownHeight),
|
|
|
+ double.PositiveInfinity,
|
|
|
+ validate: ValidateMaxDropDownHeight);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
|
|
|
+ AvaloniaProperty.Register<AutoCompleteBox, bool>(nameof(IsTextCompletionEnabled));
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
|
|
|
+ AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(nameof(ItemTemplate));
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, bool> IsDropDownOpenProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, bool>(
|
|
|
+ nameof(IsDropDownOpen),
|
|
|
+ o => o.IsDropDownOpen,
|
|
|
+ (o, v) => o.IsDropDownOpen = v);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, object> SelectedItemProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, object>(
|
|
|
+ nameof(SelectedItem),
|
|
|
+ o => o.SelectedItem,
|
|
|
+ (o, v) => o.SelectedItem = v);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, string> TextProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, string>(
|
|
|
+ nameof(Text),
|
|
|
+ o => o.Text,
|
|
|
+ (o, v) => o.Text = v);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, string> SearchTextProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, string>(
|
|
|
+ nameof(SearchText),
|
|
|
+ o => o.SearchText,
|
|
|
+ unsetValue: string.Empty);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.FilterMode" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ public static readonly StyledProperty<AutoCompleteFilterMode> FilterModeProperty =
|
|
|
+ AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterMode>(
|
|
|
+ nameof(FilterMode),
|
|
|
+ defaultValue: AutoCompleteFilterMode.StartsWith,
|
|
|
+ validate: ValidateFilterMode);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<object>> ItemFilterProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<object>>(
|
|
|
+ nameof(ItemFilter),
|
|
|
+ o => o.ItemFilter,
|
|
|
+ (o, v) => o.ItemFilter = v);
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<string>> TextFilterProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<string>>(
|
|
|
+ nameof(TextFilter),
|
|
|
+ o => o.TextFilter,
|
|
|
+ (o, v) => o.TextFilter = v,
|
|
|
+ unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The identifier for the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// dependency property.</value>
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, IEnumerable> ItemsProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, IEnumerable>(
|
|
|
+ nameof(Items),
|
|
|
+ o => o.Items,
|
|
|
+ (o, v) => o.Items = v);
|
|
|
+
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, IMemberSelector> ValueMemberSelectorProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, IMemberSelector>(
|
|
|
+ nameof(ValueMemberSelector),
|
|
|
+ o => o.ValueMemberSelector,
|
|
|
+ (o, v) => o.ValueMemberSelector = v);
|
|
|
+
|
|
|
+ public static readonly DirectProperty<AutoCompleteBox, Func<string, CancellationToken, Task<IEnumerable<object>>>> AsyncPopulatorProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<AutoCompleteBox, Func<string, CancellationToken, Task<IEnumerable<object>>>>(
|
|
|
+ nameof(AsyncPopulator),
|
|
|
+ o => o.AsyncPopulator,
|
|
|
+ (o, v) => o.AsyncPopulator = v);
|
|
|
+
|
|
|
+ private static int ValidateMinimumPrefixLength(AutoCompleteBox control, int value)
|
|
|
+ {
|
|
|
+ Contract.Requires<ArgumentOutOfRangeException>(value >= -1);
|
|
|
+
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static TimeSpan ValidateMinimumPopulateDelay(AutoCompleteBox control, TimeSpan value)
|
|
|
+ {
|
|
|
+ Contract.Requires<ArgumentOutOfRangeException>(value.TotalMilliseconds >= 0.0);
|
|
|
+
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double ValidateMaxDropDownHeight(AutoCompleteBox control, double value)
|
|
|
+ {
|
|
|
+ Contract.Requires<ArgumentOutOfRangeException>(value >= 0.0);
|
|
|
+
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool IsValidFilterMode(AutoCompleteFilterMode mode)
|
|
|
+ {
|
|
|
+ switch (mode)
|
|
|
+ {
|
|
|
+ case AutoCompleteFilterMode.None:
|
|
|
+ case AutoCompleteFilterMode.StartsWith:
|
|
|
+ case AutoCompleteFilterMode.StartsWithCaseSensitive:
|
|
|
+ case AutoCompleteFilterMode.StartsWithOrdinal:
|
|
|
+ case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive:
|
|
|
+ case AutoCompleteFilterMode.Contains:
|
|
|
+ case AutoCompleteFilterMode.ContainsCaseSensitive:
|
|
|
+ case AutoCompleteFilterMode.ContainsOrdinal:
|
|
|
+ case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive:
|
|
|
+ case AutoCompleteFilterMode.Equals:
|
|
|
+ case AutoCompleteFilterMode.EqualsCaseSensitive:
|
|
|
+ case AutoCompleteFilterMode.EqualsOrdinal:
|
|
|
+ case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive:
|
|
|
+ case AutoCompleteFilterMode.Custom:
|
|
|
+ return true;
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private static AutoCompleteFilterMode ValidateFilterMode(AutoCompleteBox control, AutoCompleteFilterMode value)
|
|
|
+ {
|
|
|
+ Contract.Requires<ArgumentException>(IsValidFilterMode(value));
|
|
|
+
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handle the change of the IsEnabled property.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">The event data.</param>
|
|
|
+ private void OnControlIsEnabledChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ bool isEnabled = (bool)e.NewValue;
|
|
|
+ if (!isEnabled)
|
|
|
+ {
|
|
|
+ IsDropDownOpen = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// MinimumPopulateDelayProperty property changed handler. Any current
|
|
|
+ /// dispatcher timer will be stopped. The timer will not be restarted
|
|
|
+ /// until the next TextUpdate call by the user.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">Event arguments.</param>
|
|
|
+ private void OnMinimumPopulateDelayChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ var newValue = (TimeSpan)e.NewValue;
|
|
|
+
|
|
|
+ // Stop any existing timer
|
|
|
+ if (_delayTimer != null)
|
|
|
+ {
|
|
|
+ _delayTimer.Stop();
|
|
|
+
|
|
|
+ if (newValue == TimeSpan.Zero)
|
|
|
+ {
|
|
|
+ _delayTimer = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newValue > TimeSpan.Zero)
|
|
|
+ {
|
|
|
+ // Create or clear a dispatcher timer instance
|
|
|
+ if (_delayTimer == null)
|
|
|
+ {
|
|
|
+ _delayTimer = new DispatcherTimer();
|
|
|
+ _delayTimer.Tick += PopulateDropDown;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set the new tick interval
|
|
|
+ _delayTimer.Interval = newValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// IsDropDownOpenProperty property changed handler.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">Event arguments.</param>
|
|
|
+ private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ // Ignore the change if requested
|
|
|
+ if (_ignorePropertyChange)
|
|
|
+ {
|
|
|
+ _ignorePropertyChange = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool oldValue = (bool)e.OldValue;
|
|
|
+ bool newValue = (bool)e.NewValue;
|
|
|
+
|
|
|
+ if (newValue)
|
|
|
+ {
|
|
|
+ TextUpdated(Text, true);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ClosingDropDown(oldValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ UpdatePseudoClasses();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnSelectedItemPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ if (_ignorePropertyChange)
|
|
|
+ {
|
|
|
+ _ignorePropertyChange = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the text display
|
|
|
+ if (_skipSelectedItemTextUpdate)
|
|
|
+ {
|
|
|
+ _skipSelectedItemTextUpdate = false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ OnSelectedItemChanged(e.NewValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fire the SelectionChanged event
|
|
|
+ List<object> removed = new List<object>();
|
|
|
+ if (e.OldValue != null)
|
|
|
+ {
|
|
|
+ removed.Add(e.OldValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<object> added = new List<object>();
|
|
|
+ if (e.NewValue != null)
|
|
|
+ {
|
|
|
+ added.Add(e.NewValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, removed, added));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// TextProperty property changed handler.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">Event arguments.</param>
|
|
|
+ private void OnTextPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ TextUpdated((string)e.NewValue, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnSearchTextPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ if (_ignorePropertyChange)
|
|
|
+ {
|
|
|
+ _ignorePropertyChange = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Ensure the property is only written when expected
|
|
|
+ if (!_allowWrite)
|
|
|
+ {
|
|
|
+ // Reset the old value before it was incorrectly written
|
|
|
+ _ignorePropertyChange = true;
|
|
|
+ SetValue(e.Property, e.OldValue);
|
|
|
+
|
|
|
+ throw new InvalidOperationException("Cannot set read-only property SearchText.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// FilterModeProperty property changed handler.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">Event arguments.</param>
|
|
|
+ private void OnFilterModePropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue;
|
|
|
+
|
|
|
+ // Sets the filter predicate for the new value
|
|
|
+ TextFilter = AutoCompleteSearch.GetFilter(mode);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// ItemFilterProperty property changed handler.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">Event arguments.</param>
|
|
|
+ private void OnItemFilterPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ AutoCompleteFilterPredicate<object> value = e.NewValue as AutoCompleteFilterPredicate<object>;
|
|
|
+
|
|
|
+ // If null, revert to the "None" predicate
|
|
|
+ if (value == null)
|
|
|
+ {
|
|
|
+ FilterMode = AutoCompleteFilterMode.None;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ FilterMode = AutoCompleteFilterMode.Custom;
|
|
|
+ TextFilter = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// ItemsSourceProperty property changed handler.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">Event arguments.</param>
|
|
|
+ private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ OnItemsChanged((IEnumerable)e.NewValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnItemTemplatePropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ if (!_settingItemTemplateFromValueMemeberBinding)
|
|
|
+ _itemTemplateIsFromValueMemeberBinding = false;
|
|
|
+ }
|
|
|
+ private void OnValueMemberBindingChanged(IBinding value)
|
|
|
+ {
|
|
|
+ if(_itemTemplateIsFromValueMemeberBinding)
|
|
|
+ {
|
|
|
+ var template =
|
|
|
+ new FuncDataTemplate(
|
|
|
+ typeof(object),
|
|
|
+ o =>
|
|
|
+ {
|
|
|
+ var control = new ContentControl();
|
|
|
+ control.Bind(ContentControl.ContentProperty, value);
|
|
|
+ return control;
|
|
|
+ });
|
|
|
+
|
|
|
+ _settingItemTemplateFromValueMemeberBinding = true;
|
|
|
+ ItemTemplate = template;
|
|
|
+ _settingItemTemplateFromValueMemeberBinding = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static AutoCompleteBox()
|
|
|
+ {
|
|
|
+ FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true);
|
|
|
+
|
|
|
+ MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnMinimumPopulateDelayChanged);
|
|
|
+ IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnIsDropDownOpenChanged);
|
|
|
+ SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSelectedItemPropertyChanged);
|
|
|
+ TextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnTextPropertyChanged);
|
|
|
+ SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSearchTextPropertyChanged);
|
|
|
+ FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnFilterModePropertyChanged);
|
|
|
+ ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemFilterPropertyChanged);
|
|
|
+ ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemsPropertyChanged);
|
|
|
+ IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnControlIsEnabledChanged);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> class.
|
|
|
+ /// </summary>
|
|
|
+ public AutoCompleteBox()
|
|
|
+ {
|
|
|
+ ClearView();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the minimum number of characters required to be entered
|
|
|
+ /// in the text box before the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> displays
|
|
|
+ /// possible matches.
|
|
|
+ /// matches.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>
|
|
|
+ /// The minimum number of characters to be entered in the text box
|
|
|
+ /// before the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
|
|
|
+ /// displays possible matches. The default is 1.
|
|
|
+ /// </value>
|
|
|
+ /// <remarks>
|
|
|
+ /// If you set MinimumPrefixLength to -1, the AutoCompleteBox will
|
|
|
+ /// not provide possible matches. There is no maximum value, but
|
|
|
+ /// setting MinimumPrefixLength to value that is too large will
|
|
|
+ /// prevent the AutoCompleteBox from providing possible matches as well.
|
|
|
+ /// </remarks>
|
|
|
+ public int MinimumPrefixLength
|
|
|
+ {
|
|
|
+ get { return GetValue(MinimumPrefixLengthProperty); }
|
|
|
+ set { SetValue(MinimumPrefixLengthProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether the first possible match
|
|
|
+ /// found during the filtering process will be displayed automatically
|
|
|
+ /// in the text box.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>
|
|
|
+ /// True if the first possible match found will be displayed
|
|
|
+ /// automatically in the text box; otherwise, false. The default is
|
|
|
+ /// false.
|
|
|
+ /// </value>
|
|
|
+ public bool IsTextCompletionEnabled
|
|
|
+ {
|
|
|
+ get { return GetValue(IsTextCompletionEnabledProperty); }
|
|
|
+ set { SetValue(IsTextCompletionEnabledProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the <see cref="T:Avalonia.DataTemplate" /> used
|
|
|
+ /// to display each item in the drop-down portion of the control.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The <see cref="T:Avalonia.DataTemplate" /> used to
|
|
|
+ /// display each item in the drop-down. The default is null.</value>
|
|
|
+ /// <remarks>
|
|
|
+ /// You use the ItemTemplate property to specify the visualization
|
|
|
+ /// of the data objects in the drop-down portion of the AutoCompleteBox
|
|
|
+ /// control. If your AutoCompleteBox is bound to a collection and you
|
|
|
+ /// do not provide specific display instructions by using a
|
|
|
+ /// DataTemplate, the resulting UI of each item is a string
|
|
|
+ /// representation of each object in the underlying collection.
|
|
|
+ /// </remarks>
|
|
|
+ public IDataTemplate ItemTemplate
|
|
|
+ {
|
|
|
+ get { return GetValue(ItemTemplateProperty); }
|
|
|
+ set { SetValue(ItemTemplateProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the minimum delay, after text is typed
|
|
|
+ /// in the text box before the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
|
|
|
+ /// populates the list of possible matches in the drop-down.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The minimum delay, after text is typed in
|
|
|
+ /// the text box, but before the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> populates
|
|
|
+ /// the list of possible matches in the drop-down. The default is 0.</value>
|
|
|
+ public TimeSpan MinimumPopulateDelay
|
|
|
+ {
|
|
|
+ get { return GetValue(MinimumPopulateDelayProperty); }
|
|
|
+ set { SetValue(MinimumPopulateDelayProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the maximum height of the drop-down portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The maximum height of the drop-down portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
|
|
|
+ /// The default is <see cref="F:System.Double.PositiveInfinity" />.</value>
|
|
|
+ /// <exception cref="T:System.ArgumentException">The specified value is less than 0.</exception>
|
|
|
+ public double MaxDropDownHeight
|
|
|
+ {
|
|
|
+ get { return GetValue(MaxDropDownHeightProperty); }
|
|
|
+ set { SetValue(MaxDropDownHeightProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether the drop-down portion of
|
|
|
+ /// the control is open.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>
|
|
|
+ /// True if the drop-down is open; otherwise, false. The default is
|
|
|
+ /// false.
|
|
|
+ /// </value>
|
|
|
+ public bool IsDropDownOpen
|
|
|
+ {
|
|
|
+ get { return _isDropDownOpen; }
|
|
|
+ set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the <see cref="T:Avalonia.Data.Binding" /> that
|
|
|
+ /// is used to get the values for display in the text portion of
|
|
|
+ /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
|
|
|
+ /// control.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The <see cref="T:Avalonia.Data.IBinding" /> object used
|
|
|
+ /// when binding to a collection property.</value>
|
|
|
+ [AssignBinding]
|
|
|
+ public IBinding ValueMemberBinding
|
|
|
+ {
|
|
|
+ get { return _valueBindingEvaluator?.ValueBinding; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (ValueMemberBinding != value)
|
|
|
+ {
|
|
|
+ _valueBindingEvaluator = new BindingEvaluator<string>(value);
|
|
|
+ OnValueMemberBindingChanged(value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the MemberSelector that is used to get values for
|
|
|
+ /// display in the text portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The MemberSelector that is used to get values for display in
|
|
|
+ /// the text portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
|
|
|
+ public IMemberSelector ValueMemberSelector
|
|
|
+ {
|
|
|
+ get { return _valueMemberSelector; }
|
|
|
+ set { SetAndRaise(ValueMemberSelectorProperty, ref _valueMemberSelector, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the selected item in the drop-down.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The selected item in the drop-down.</value>
|
|
|
+ /// <remarks>
|
|
|
+ /// If the IsTextCompletionEnabled property is true and text typed by
|
|
|
+ /// the user matches an item in the ItemsSource collection, which is
|
|
|
+ /// then displayed in the text box, the SelectedItem property will be
|
|
|
+ /// a null reference.
|
|
|
+ /// </remarks>
|
|
|
+ public object SelectedItem
|
|
|
+ {
|
|
|
+ get { return _selectedItem; }
|
|
|
+ set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the text in the text box portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The text in the text box portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
|
|
|
+ public string Text
|
|
|
+ {
|
|
|
+ get { return _text; }
|
|
|
+ set { SetAndRaise(TextProperty, ref _text, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the text that is used to filter items in the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// item collection.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The text that is used to filter items in the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// item collection.</value>
|
|
|
+ /// <remarks>
|
|
|
+ /// The SearchText value is typically the same as the
|
|
|
+ /// Text property, but is set after the TextChanged event occurs
|
|
|
+ /// and before the Populating event.
|
|
|
+ /// </remarks>
|
|
|
+ public string SearchText
|
|
|
+ {
|
|
|
+ get { return _searchText; }
|
|
|
+ private set
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ _allowWrite = true;
|
|
|
+ SetAndRaise(SearchTextProperty, ref _searchText, value);
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ _allowWrite = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets how the text in the text box is used to filter items
|
|
|
+ /// specified by the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// property for display in the drop-down.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>One of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />
|
|
|
+ /// values The default is
|
|
|
+ /// <see cref="F:Avalonia.Controls.AutoCompleteFilterMode.StartsWith" />.</value>
|
|
|
+ /// <exception cref="T:System.ArgumentException">The specified value is
|
|
|
+ /// not a valid
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />.</exception>
|
|
|
+ /// <remarks>
|
|
|
+ /// Use the FilterMode property to specify how possible matches are
|
|
|
+ /// filtered. For example, possible matches can be filtered in a
|
|
|
+ /// predefined or custom way. The search mode is automatically set to
|
|
|
+ /// Custom if you set the ItemFilter property.
|
|
|
+ /// </remarks>
|
|
|
+ public AutoCompleteFilterMode FilterMode
|
|
|
+ {
|
|
|
+ get { return GetValue(FilterModeProperty); }
|
|
|
+ set { SetValue(FilterModeProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ public string Watermark
|
|
|
+ {
|
|
|
+ get { return GetValue(WatermarkProperty); }
|
|
|
+ set { SetValue(WatermarkProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the custom method that uses user-entered text to filter
|
|
|
+ /// the items specified by the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// property for display in the drop-down.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The custom method that uses the user-entered text to filter
|
|
|
+ /// the items specified by the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// property. The default is null.</value>
|
|
|
+ /// <remarks>
|
|
|
+ /// The filter mode is automatically set to Custom if you set the
|
|
|
+ /// ItemFilter property.
|
|
|
+ /// </remarks>
|
|
|
+ public AutoCompleteFilterPredicate<object> ItemFilter
|
|
|
+ {
|
|
|
+ get { return _itemFilter; }
|
|
|
+ set { SetAndRaise(ItemFilterProperty, ref _itemFilter, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the custom method that uses the user-entered text to
|
|
|
+ /// filter items specified by the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// property in a text-based way for display in the drop-down.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The custom method that uses the user-entered text to filter
|
|
|
+ /// items specified by the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
|
|
|
+ /// property in a text-based way for display in the drop-down.</value>
|
|
|
+ /// <remarks>
|
|
|
+ /// The search mode is automatically set to Custom if you set the
|
|
|
+ /// TextFilter property.
|
|
|
+ /// </remarks>
|
|
|
+ public AutoCompleteFilterPredicate<string> TextFilter
|
|
|
+ {
|
|
|
+ get { return _textFilter; }
|
|
|
+ set { SetAndRaise(TextFilterProperty, ref _textFilter, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Func<string, CancellationToken, Task<IEnumerable<object>>> AsyncPopulator
|
|
|
+ {
|
|
|
+ get { return _asyncPopulator; }
|
|
|
+ set { SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a collection that is used to generate the items for the
|
|
|
+ /// drop-down portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The collection that is used to generate the items of the
|
|
|
+ /// drop-down portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
|
|
|
+ public IEnumerable Items
|
|
|
+ {
|
|
|
+ get { return _itemsEnumerable; }
|
|
|
+ set { SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the drop down popup control.
|
|
|
+ /// </summary>
|
|
|
+ private Popup DropDownPopup { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the Text template part.
|
|
|
+ /// </summary>
|
|
|
+ private TextBox TextBox
|
|
|
+ {
|
|
|
+ get { return _textBox; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _textBoxSubscriptions?.Dispose();
|
|
|
+ _textBox = value;
|
|
|
+
|
|
|
+ // Attach handlers
|
|
|
+ if (_textBox != null)
|
|
|
+ {
|
|
|
+ _textBoxSubscriptions =
|
|
|
+ _textBox.GetObservable(TextBox.TextProperty)
|
|
|
+ .Subscribe(_ => OnTextBoxTextChanged());
|
|
|
+
|
|
|
+ if (Text != null)
|
|
|
+ {
|
|
|
+ UpdateTextValue(Text);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private int TextBoxSelectionStart
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ if (TextBox != null)
|
|
|
+ {
|
|
|
+ return Math.Min(TextBox.SelectionStart, TextBox.SelectionEnd);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private int TextBoxSelectionLength
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ if (TextBox != null)
|
|
|
+ {
|
|
|
+ return Math.Abs(TextBox.SelectionEnd - TextBox.SelectionStart);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the selection adapter used to populate the drop-down
|
|
|
+ /// with a list of selectable items.
|
|
|
+ /// </summary>
|
|
|
+ /// <value>The selection adapter used to populate the drop-down with a
|
|
|
+ /// list of selectable items.</value>
|
|
|
+ /// <remarks>
|
|
|
+ /// You can use this property when you create an automation peer to
|
|
|
+ /// use with AutoCompleteBox or deriving from AutoCompleteBox to
|
|
|
+ /// create a custom control.
|
|
|
+ /// </remarks>
|
|
|
+ protected ISelectionAdapter SelectionAdapter
|
|
|
+ {
|
|
|
+ get { return _adapter; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (_adapter != null)
|
|
|
+ {
|
|
|
+ _adapter.SelectionChanged -= OnAdapterSelectionChanged;
|
|
|
+ _adapter.Commit -= OnAdapterSelectionComplete;
|
|
|
+ _adapter.Cancel -= OnAdapterSelectionCanceled;
|
|
|
+ _adapter.Cancel -= OnAdapterSelectionComplete;
|
|
|
+ _adapter.Items = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ _adapter = value;
|
|
|
+
|
|
|
+ if (_adapter != null)
|
|
|
+ {
|
|
|
+ _adapter.SelectionChanged += OnAdapterSelectionChanged;
|
|
|
+ _adapter.Commit += OnAdapterSelectionComplete;
|
|
|
+ _adapter.Cancel += OnAdapterSelectionCanceled;
|
|
|
+ _adapter.Cancel += OnAdapterSelectionComplete;
|
|
|
+ _adapter.Items = _view;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Returns the
|
|
|
+ /// <see cref="T:Avalonia.Controls.ISelectionAdapter" /> part, if
|
|
|
+ /// possible.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>
|
|
|
+ /// A <see cref="T:Avalonia.Controls.ISelectionAdapter" /> object,
|
|
|
+ /// if possible. Otherwise, null.
|
|
|
+ /// </returns>
|
|
|
+ protected virtual ISelectionAdapter GetSelectionAdapterPart(INameScope nameScope)
|
|
|
+ {
|
|
|
+ ISelectionAdapter adapter = null;
|
|
|
+ SelectingItemsControl selector = nameScope.Find<SelectingItemsControl>(ElementSelector);
|
|
|
+ if (selector != null)
|
|
|
+ {
|
|
|
+ // Check if it is already an IItemsSelector
|
|
|
+ adapter = selector as ISelectionAdapter;
|
|
|
+ if (adapter == null)
|
|
|
+ {
|
|
|
+ // Built in support for wrapping a Selector control
|
|
|
+ adapter = new SelectingItemsControlSelectionAdapter(selector);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (adapter == null)
|
|
|
+ {
|
|
|
+ adapter = nameScope.Find<ISelectionAdapter>(ElementSelectionAdapter);
|
|
|
+ }
|
|
|
+ return adapter;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Builds the visual tree for the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
|
|
|
+ /// when a new template is applied.
|
|
|
+ /// </summary>
|
|
|
+ protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
|
|
|
+ {
|
|
|
+
|
|
|
+ if (DropDownPopup != null)
|
|
|
+ {
|
|
|
+ DropDownPopup.Closed -= DropDownPopup_Closed;
|
|
|
+ DropDownPopup = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set the template parts. Individual part setters remove and add
|
|
|
+ // any event handlers.
|
|
|
+ Popup popup = e.NameScope.Find<Popup>(ElementPopup);
|
|
|
+ if (popup != null)
|
|
|
+ {
|
|
|
+ DropDownPopup = popup;
|
|
|
+ DropDownPopup.Closed += DropDownPopup_Closed;
|
|
|
+ }
|
|
|
+
|
|
|
+ SelectionAdapter = GetSelectionAdapterPart(e.NameScope);
|
|
|
+ TextBox = e.NameScope.Find<TextBox>(ElementTextBox);
|
|
|
+
|
|
|
+ // If the drop down property indicates that the popup is open,
|
|
|
+ // flip its value to invoke the changed handler.
|
|
|
+ if (IsDropDownOpen && DropDownPopup != null && !DropDownPopup.IsOpen)
|
|
|
+ {
|
|
|
+ OpeningDropDown(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ base.OnTemplateApplied(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Provides handling for the
|
|
|
+ /// <see cref="E:Avalonia.InputElement.KeyDown" /> event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected override void OnKeyDown(KeyEventArgs e)
|
|
|
+ {
|
|
|
+ Contract.Requires<ArgumentNullException>(e != null);
|
|
|
+
|
|
|
+ base.OnKeyDown(e);
|
|
|
+
|
|
|
+ if (e.Handled || !IsEnabled)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // The drop down is open, pass along the key event arguments to the
|
|
|
+ // selection adapter. If it isn't handled by the adapter's logic,
|
|
|
+ // then we handle some simple navigation scenarios for controlling
|
|
|
+ // the drop down.
|
|
|
+ if (IsDropDownOpen)
|
|
|
+ {
|
|
|
+ if (SelectionAdapter != null)
|
|
|
+ {
|
|
|
+ SelectionAdapter.HandleKeyDown(e);
|
|
|
+ if (e.Handled)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e.Key == Key.Escape)
|
|
|
+ {
|
|
|
+ OnAdapterSelectionCanceled(this, new RoutedEventArgs());
|
|
|
+ e.Handled = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // The drop down is not open, the Down key will toggle it open.
|
|
|
+ if (e.Key == Key.Down)
|
|
|
+ {
|
|
|
+ IsDropDownOpen = true;
|
|
|
+ e.Handled = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Standard drop down navigation
|
|
|
+ switch (e.Key)
|
|
|
+ {
|
|
|
+ case Key.F4:
|
|
|
+ IsDropDownOpen = !IsDropDownOpen;
|
|
|
+ e.Handled = true;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case Key.Enter:
|
|
|
+ OnAdapterSelectionComplete(this, new RoutedEventArgs());
|
|
|
+ e.Handled = true;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Provides handling for the
|
|
|
+ /// <see cref="E:Avalonia.UIElement.GotFocus" /> event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected override void OnGotFocus(GotFocusEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnGotFocus(e);
|
|
|
+ FocusChanged(HasFocus());
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Provides handling for the
|
|
|
+ /// <see cref="E:Avalonia.UIElement.LostFocus" /> event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected override void OnLostFocus(RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnLostFocus(e);
|
|
|
+ FocusChanged(HasFocus());
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Determines whether the text box or drop-down portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control has
|
|
|
+ /// focus.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>true to indicate the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus;
|
|
|
+ /// otherwise, false.</returns>
|
|
|
+ protected bool HasFocus()
|
|
|
+ {
|
|
|
+ IVisual focused = FocusManager.Instance.Current;
|
|
|
+
|
|
|
+ while (focused != null)
|
|
|
+ {
|
|
|
+ if (object.ReferenceEquals(focused, this))
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // This helps deal with popups that may not be in the same
|
|
|
+ // visual tree
|
|
|
+ IVisual parent = focused.GetVisualParent();
|
|
|
+ if (parent == null)
|
|
|
+ {
|
|
|
+ // Try the logical parent.
|
|
|
+ IControl element = focused as IControl;
|
|
|
+ if (element != null)
|
|
|
+ {
|
|
|
+ parent = element.Parent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ focused = parent;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handles the FocusChanged event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="hasFocus">A value indicating whether the control
|
|
|
+ /// currently has the focus.</param>
|
|
|
+ private void FocusChanged(bool hasFocus)
|
|
|
+ {
|
|
|
+ // The OnGotFocus & OnLostFocus are asynchronously and cannot
|
|
|
+ // reliably tell you that have the focus. All they do is let you
|
|
|
+ // know that the focus changed sometime in the past. To determine
|
|
|
+ // if you currently have the focus you need to do consult the
|
|
|
+ // FocusManager (see HasFocus()).
|
|
|
+
|
|
|
+ bool wasFocused = _isFocused;
|
|
|
+ _isFocused = hasFocus;
|
|
|
+
|
|
|
+ if (hasFocus)
|
|
|
+ {
|
|
|
+
|
|
|
+ if (!wasFocused && TextBox != null && TextBoxSelectionLength <= 0)
|
|
|
+ {
|
|
|
+ TextBox.Focus();
|
|
|
+ TextBox.SelectionStart = 0;
|
|
|
+ TextBox.SelectionEnd = TextBox.Text?.Length ?? 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ IsDropDownOpen = false;
|
|
|
+ _userCalledPopulate = false;
|
|
|
+ ClearTextBoxSelection();
|
|
|
+ }
|
|
|
+
|
|
|
+ _isFocused = hasFocus;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Occurs when the text in the text box portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> changes.
|
|
|
+ /// </summary>
|
|
|
+ public event EventHandler TextChanged;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Occurs when the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> is
|
|
|
+ /// populating the drop-down with possible matches based on the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
|
|
|
+ /// property.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// If the event is canceled, by setting the PopulatingEventArgs.Cancel
|
|
|
+ /// property to true, the AutoCompleteBox will not automatically
|
|
|
+ /// populate the selection adapter contained in the drop-down.
|
|
|
+ /// In this case, if you want possible matches to appear, you must
|
|
|
+ /// provide the logic for populating the selection adapter.
|
|
|
+ /// </remarks>
|
|
|
+ public event EventHandler<PopulatingEventArgs> Populating;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Occurs when the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has
|
|
|
+ /// populated the drop-down with possible matches based on the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
|
|
|
+ /// property.
|
|
|
+ /// </summary>
|
|
|
+ public event EventHandler<PopulatedEventArgs> Populated;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Occurs when the value of the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
|
|
|
+ /// property is changing from false to true.
|
|
|
+ /// </summary>
|
|
|
+ public event EventHandler<CancelEventArgs> DropDownOpening;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Occurs when the value of the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
|
|
|
+ /// property has changed from false to true and the drop-down is open.
|
|
|
+ /// </summary>
|
|
|
+ public event EventHandler DropDownOpened;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Occurs when the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
|
|
|
+ /// property is changing from true to false.
|
|
|
+ /// </summary>
|
|
|
+ public event EventHandler<CancelEventArgs> DropDownClosing;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Occurs when the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
|
|
|
+ /// property was changed from true to false and the drop-down is open.
|
|
|
+ /// </summary>
|
|
|
+ public event EventHandler DropDownClosed;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Occurs when the selected item in the drop-down portion of the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has
|
|
|
+ /// changed.
|
|
|
+ /// </summary>
|
|
|
+ public event EventHandler<SelectionChangedEventArgs> SelectionChanged
|
|
|
+ {
|
|
|
+ add { AddHandler(SelectionChangedEvent, value); }
|
|
|
+ remove { RemoveHandler(SelectionChangedEvent, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Raises the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A
|
|
|
+ /// <see cref="T:Avalonia.Controls.PopulatingEventArgs" /> that
|
|
|
+ /// contains the event data.</param>
|
|
|
+ protected virtual void OnPopulating(PopulatingEventArgs e)
|
|
|
+ {
|
|
|
+ Populating?.Invoke(this, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Raises the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A
|
|
|
+ /// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected virtual void OnPopulated(PopulatedEventArgs e)
|
|
|
+ {
|
|
|
+ Populated?.Invoke(this, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Raises the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.SelectionChanged" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A
|
|
|
+ /// <see cref="T:Avalonia.Controls.SelectionChangedEventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ RaiseEvent(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Raises the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownOpening" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A
|
|
|
+ /// <see cref="T:Avalonia.Controls.CancelEventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected virtual void OnDropDownOpening(CancelEventArgs e)
|
|
|
+ {
|
|
|
+ DropDownOpening?.Invoke(this, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Raises the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownOpened" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A
|
|
|
+ /// <see cref="T:System.EventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected virtual void OnDropDownOpened(EventArgs e)
|
|
|
+ {
|
|
|
+ DropDownOpened?.Invoke(this, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Raises the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownClosing" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A
|
|
|
+ /// <see cref="T:Avalonia.Controls.CancelEventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected virtual void OnDropDownClosing(CancelEventArgs e)
|
|
|
+ {
|
|
|
+ DropDownClosing?.Invoke(this, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Raises the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownClosed" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A
|
|
|
+ /// <see cref="T:System.EventArgs" />
|
|
|
+ /// which contains the event data.</param>
|
|
|
+ protected virtual void OnDropDownClosed(EventArgs e)
|
|
|
+ {
|
|
|
+ DropDownClosed?.Invoke(this, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Raises the
|
|
|
+ /// <see cref="E:Avalonia.Controls.AutoCompleteBox.TextChanged" />
|
|
|
+ /// event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
|
|
|
+ /// that contains the event data.</param>
|
|
|
+ protected virtual void OnTextChanged(RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ TextChanged?.Invoke(this, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Begin closing the drop-down.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="oldValue">The original value.</param>
|
|
|
+ private void ClosingDropDown(bool oldValue)
|
|
|
+ {
|
|
|
+ var args = new CancelEventArgs();
|
|
|
+ OnDropDownClosing(args);
|
|
|
+
|
|
|
+ if (args.Cancel)
|
|
|
+ {
|
|
|
+ _ignorePropertyChange = true;
|
|
|
+ SetValue(IsDropDownOpenProperty, oldValue);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ CloseDropDown();
|
|
|
+ }
|
|
|
+
|
|
|
+ UpdatePseudoClasses();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Begin opening the drop down by firing cancelable events, opening the
|
|
|
+ /// drop-down or reverting, depending on the event argument values.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="oldValue">The original value, if needed for a revert.</param>
|
|
|
+ private void OpeningDropDown(bool oldValue)
|
|
|
+ {
|
|
|
+ var args = new CancelEventArgs();
|
|
|
+
|
|
|
+ // Opening
|
|
|
+ OnDropDownOpening(args);
|
|
|
+
|
|
|
+ if (args.Cancel)
|
|
|
+ {
|
|
|
+ _ignorePropertyChange = true;
|
|
|
+ SetValue(IsDropDownOpenProperty, oldValue);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ OpenDropDown();
|
|
|
+ }
|
|
|
+
|
|
|
+ UpdatePseudoClasses();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Connects to the DropDownPopup Closed event.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sender">The source object.</param>
|
|
|
+ /// <param name="e">The event data.</param>
|
|
|
+ private void DropDownPopup_Closed(object sender, EventArgs e)
|
|
|
+ {
|
|
|
+ // Force the drop down dependency property to be false.
|
|
|
+ if (IsDropDownOpen)
|
|
|
+ {
|
|
|
+ IsDropDownOpen = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fire the DropDownClosed event
|
|
|
+ if (_popupHasOpened)
|
|
|
+ {
|
|
|
+ OnDropDownClosed(EventArgs.Empty);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handles the timer tick when using a populate delay.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sender">The source object.</param>
|
|
|
+ /// <param name="e">The event arguments.</param>
|
|
|
+ private void PopulateDropDown(object sender, EventArgs e)
|
|
|
+ {
|
|
|
+ if (_delayTimer != null)
|
|
|
+ {
|
|
|
+ _delayTimer.Stop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the prefix/search text.
|
|
|
+ SearchText = Text;
|
|
|
+
|
|
|
+ if(TryPopulateAsync(SearchText))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // The Populated event enables advanced, custom filtering. The
|
|
|
+ // client needs to directly update the ItemsSource collection or
|
|
|
+ // call the Populate method on the control to continue the
|
|
|
+ // display process if Cancel is set to true.
|
|
|
+ PopulatingEventArgs populating = new PopulatingEventArgs(SearchText);
|
|
|
+ OnPopulating(populating);
|
|
|
+ if (!populating.Cancel)
|
|
|
+ {
|
|
|
+ PopulateComplete();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private bool TryPopulateAsync(string searchText)
|
|
|
+ {
|
|
|
+ _populationCancellationTokenSource?.Cancel(false);
|
|
|
+ _populationCancellationTokenSource?.Dispose();
|
|
|
+ _populationCancellationTokenSource = null;
|
|
|
+
|
|
|
+ if(_asyncPopulator == null)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ _populationCancellationTokenSource = new CancellationTokenSource();
|
|
|
+ var task = PopulateAsync(searchText, _populationCancellationTokenSource.Token);
|
|
|
+ if (task.Status == TaskStatus.Created)
|
|
|
+ task.Start();
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ private async Task PopulateAsync(string searchText, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ IEnumerable<object> result = await _asyncPopulator.Invoke(searchText, cancellationToken);
|
|
|
+ var resultList = result.ToList();
|
|
|
+
|
|
|
+ if (cancellationToken.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
+ {
|
|
|
+ if (!cancellationToken.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ Items = resultList;
|
|
|
+ PopulateComplete();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ catch (TaskCanceledException)
|
|
|
+ { }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ _populationCancellationTokenSource?.Dispose();
|
|
|
+ _populationCancellationTokenSource = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Private method that directly opens the popup, checks the expander
|
|
|
+ /// button, and then fires the Opened event.
|
|
|
+ /// </summary>
|
|
|
+ private void OpenDropDown()
|
|
|
+ {
|
|
|
+ if (DropDownPopup != null)
|
|
|
+ {
|
|
|
+ DropDownPopup.IsOpen = true;
|
|
|
+ }
|
|
|
+ _popupHasOpened = true;
|
|
|
+ OnDropDownOpened(EventArgs.Empty);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Private method that directly closes the popup, flips the Checked
|
|
|
+ /// value, and then fires the Closed event.
|
|
|
+ /// </summary>
|
|
|
+ private void CloseDropDown()
|
|
|
+ {
|
|
|
+ if (_popupHasOpened)
|
|
|
+ {
|
|
|
+ if (SelectionAdapter != null)
|
|
|
+ {
|
|
|
+ SelectionAdapter.SelectedItem = null;
|
|
|
+ }
|
|
|
+ if (DropDownPopup != null)
|
|
|
+ {
|
|
|
+ DropDownPopup.IsOpen = false;
|
|
|
+ }
|
|
|
+ OnDropDownClosed(EventArgs.Empty);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Formats an Item for text comparisons based on Converter
|
|
|
+ /// and ConverterCulture properties.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="value">The object to format.</param>
|
|
|
+ /// <param name="clearDataContext">A value indicating whether to clear
|
|
|
+ /// the data context after the lookup is performed.</param>
|
|
|
+ /// <returns>Formatted Value.</returns>
|
|
|
+ private string FormatValue(object value, bool clearDataContext)
|
|
|
+ {
|
|
|
+ string result = FormatValue(value);
|
|
|
+ if(clearDataContext && _valueBindingEvaluator != null)
|
|
|
+ {
|
|
|
+ _valueBindingEvaluator.ClearDataContext();
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Converts the specified object to a string by using the
|
|
|
+ /// <see cref="P:Avalonia.Data.Binding.Converter" /> and
|
|
|
+ /// <see cref="P:Avalonia.Data.Binding.ConverterCulture" /> values
|
|
|
+ /// of the binding object specified by the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ValueMemberBinding" />
|
|
|
+ /// property.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="value">The object to format as a string.</param>
|
|
|
+ /// <returns>The string representation of the specified object.</returns>
|
|
|
+ /// <remarks>
|
|
|
+ /// Override this method to provide a custom string conversion.
|
|
|
+ /// </remarks>
|
|
|
+ protected virtual string FormatValue(object value)
|
|
|
+ {
|
|
|
+ if (_valueBindingEvaluator != null)
|
|
|
+ {
|
|
|
+ return _valueBindingEvaluator.GetDynamicValue(value) ?? String.Empty;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_valueMemberSelector != null)
|
|
|
+ {
|
|
|
+ value = _valueMemberSelector.Select(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ return value == null ? String.Empty : value.ToString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handle the TextChanged event that is directly attached to the
|
|
|
+ /// TextBox part. This ensures that only user initiated actions will
|
|
|
+ /// result in an AutoCompleteBox suggestion and operation.
|
|
|
+ /// </summary>
|
|
|
+ private void OnTextBoxTextChanged()
|
|
|
+ {
|
|
|
+ //Uses Dispatcher.Post to allow the TextBox selection to update before processing
|
|
|
+ Dispatcher.UIThread.Post(() =>
|
|
|
+ {
|
|
|
+ // Call the central updated text method as a user-initiated action
|
|
|
+ TextUpdated(_textBox.Text, true);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Updates both the text box value and underlying text dependency
|
|
|
+ /// property value if and when they change. Automatically fires the
|
|
|
+ /// text changed events when there is a change.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="value">The new string value.</param>
|
|
|
+ private void UpdateTextValue(string value)
|
|
|
+ {
|
|
|
+ UpdateTextValue(value, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Updates both the text box value and underlying text dependency
|
|
|
+ /// property value if and when they change. Automatically fires the
|
|
|
+ /// text changed events when there is a change.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="value">The new string value.</param>
|
|
|
+ /// <param name="userInitiated">A nullable bool value indicating whether
|
|
|
+ /// the action was user initiated. In a user initiated mode, the
|
|
|
+ /// underlying text dependency property is updated. In a non-user
|
|
|
+ /// interaction, the text box value is updated. When user initiated is
|
|
|
+ /// null, all values are updated.</param>
|
|
|
+ private void UpdateTextValue(string value, bool? userInitiated)
|
|
|
+ {
|
|
|
+ bool callTextChanged = false;
|
|
|
+ // Update the Text dependency property
|
|
|
+ if ((userInitiated == null || userInitiated == true) && Text != value)
|
|
|
+ {
|
|
|
+ _ignoreTextPropertyChange++;
|
|
|
+ Text = value;
|
|
|
+ callTextChanged = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the TextBox's Text dependency property
|
|
|
+ if ((userInitiated == null || userInitiated == false) && TextBox != null && TextBox.Text != value)
|
|
|
+ {
|
|
|
+ _ignoreTextPropertyChange++;
|
|
|
+ TextBox.Text = value ?? string.Empty;
|
|
|
+
|
|
|
+ // Text dependency property value was set, fire event
|
|
|
+ if (!callTextChanged && (Text == value || Text == null))
|
|
|
+ {
|
|
|
+ callTextChanged = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (callTextChanged)
|
|
|
+ {
|
|
|
+ OnTextChanged(new RoutedEventArgs());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handle the update of the text for the control from any source,
|
|
|
+ /// including the TextBox part and the Text dependency property.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="newText">The new text.</param>
|
|
|
+ /// <param name="userInitiated">A value indicating whether the update
|
|
|
+ /// is a user-initiated action. This should be a True value when the
|
|
|
+ /// TextUpdated method is called from a TextBox event handler.</param>
|
|
|
+ private void TextUpdated(string newText, bool userInitiated)
|
|
|
+ {
|
|
|
+ // Only process this event if it is coming from someone outside
|
|
|
+ // setting the Text dependency property directly.
|
|
|
+ if (_ignoreTextPropertyChange > 0)
|
|
|
+ {
|
|
|
+ _ignoreTextPropertyChange--;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newText == null)
|
|
|
+ {
|
|
|
+ newText = string.Empty;
|
|
|
+ }
|
|
|
+
|
|
|
+ // The TextBox.TextChanged event was not firing immediately and
|
|
|
+ // was causing an immediate update, even with wrapping. If there is
|
|
|
+ // a selection currently, no update should happen.
|
|
|
+ if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != TextBox.Text.Length)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Evaluate the conditions needed for completion.
|
|
|
+ // 1. Minimum prefix length
|
|
|
+ // 2. If a delay timer is in use, use it
|
|
|
+ bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0;
|
|
|
+ _userCalledPopulate = populateReady ? userInitiated : false;
|
|
|
+
|
|
|
+ // Update the interface and values only as necessary
|
|
|
+ UpdateTextValue(newText, userInitiated);
|
|
|
+
|
|
|
+ if (populateReady)
|
|
|
+ {
|
|
|
+ _ignoreTextSelectionChange = true;
|
|
|
+
|
|
|
+ if (_delayTimer != null)
|
|
|
+ {
|
|
|
+ _delayTimer.Start();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PopulateDropDown(this, EventArgs.Empty);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ SearchText = string.Empty;
|
|
|
+ if (SelectedItem != null)
|
|
|
+ {
|
|
|
+ _skipSelectedItemTextUpdate = true;
|
|
|
+ }
|
|
|
+ SelectedItem = null;
|
|
|
+ if (IsDropDownOpen)
|
|
|
+ {
|
|
|
+ IsDropDownOpen = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A simple helper method to clear the view and ensure that a view
|
|
|
+ /// object is always present and not null.
|
|
|
+ /// </summary>
|
|
|
+ private void ClearView()
|
|
|
+ {
|
|
|
+ if (_view == null)
|
|
|
+ {
|
|
|
+ _view = new AvaloniaList<object>();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _view.Clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Walks through the items enumeration. Performance is not going to be
|
|
|
+ /// perfect with the current implementation.
|
|
|
+ /// </summary>
|
|
|
+ private void RefreshView()
|
|
|
+ {
|
|
|
+ if (_items == null)
|
|
|
+ {
|
|
|
+ ClearView();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Cache the current text value
|
|
|
+ string text = Text ?? string.Empty;
|
|
|
+
|
|
|
+ // Determine if any filtering mode is on
|
|
|
+ bool stringFiltering = TextFilter != null;
|
|
|
+ bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;
|
|
|
+
|
|
|
+ int view_index = 0;
|
|
|
+ int view_count = _view.Count;
|
|
|
+ List<object> items = _items;
|
|
|
+ foreach (object item in items)
|
|
|
+ {
|
|
|
+ bool inResults = !(stringFiltering || objectFiltering);
|
|
|
+ if (!inResults)
|
|
|
+ {
|
|
|
+ inResults = stringFiltering ? TextFilter(text, FormatValue(item)) : ItemFilter(text, item);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (view_count > view_index && inResults && _view[view_index] == item)
|
|
|
+ {
|
|
|
+ // Item is still in the view
|
|
|
+ view_index++;
|
|
|
+ }
|
|
|
+ else if (inResults)
|
|
|
+ {
|
|
|
+ // Insert the item
|
|
|
+ if (view_count > view_index && _view[view_index] != item)
|
|
|
+ {
|
|
|
+ // Replace item
|
|
|
+ // Unfortunately replacing via index throws a fatal
|
|
|
+ // exception: View[view_index] = item;
|
|
|
+ // Cost: O(n) vs O(1)
|
|
|
+ _view.RemoveAt(view_index);
|
|
|
+ _view.Insert(view_index, item);
|
|
|
+ view_index++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Add the item
|
|
|
+ if (view_index == view_count)
|
|
|
+ {
|
|
|
+ // Constant time is preferred (Add).
|
|
|
+ _view.Add(item);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _view.Insert(view_index, item);
|
|
|
+ }
|
|
|
+ view_index++;
|
|
|
+ view_count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (view_count > view_index && _view[view_index] == item)
|
|
|
+ {
|
|
|
+ // Remove the item
|
|
|
+ _view.RemoveAt(view_index);
|
|
|
+ view_count--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Clear the evaluator to discard a reference to the last item
|
|
|
+ if (_valueBindingEvaluator != null)
|
|
|
+ {
|
|
|
+ _valueBindingEvaluator.ClearDataContext();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handle any change to the ItemsSource dependency property, update
|
|
|
+ /// the underlying ObservableCollection view, and set the selection
|
|
|
+ /// adapter's ItemsSource to the view if appropriate.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="newValue">The new enumerable reference.</param>
|
|
|
+ private void OnItemsChanged(IEnumerable newValue)
|
|
|
+ {
|
|
|
+ // Remove handler for oldValue.CollectionChanged (if present)
|
|
|
+ _collectionChangeSubscription?.Dispose();
|
|
|
+ _collectionChangeSubscription = null;
|
|
|
+
|
|
|
+ // Add handler for newValue.CollectionChanged (if possible)
|
|
|
+ if (newValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
|
|
|
+ {
|
|
|
+ _collectionChangeSubscription = newValueINotifyCollectionChanged.WeakSubscribe(ItemsCollectionChanged);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Store a local cached copy of the data
|
|
|
+ _items = newValue == null ? null : new List<object>(newValue.Cast<object>().ToList());
|
|
|
+
|
|
|
+ // Clear and set the view on the selection adapter
|
|
|
+ ClearView();
|
|
|
+ if (SelectionAdapter != null && SelectionAdapter.Items != _view)
|
|
|
+ {
|
|
|
+ SelectionAdapter.Items = _view;
|
|
|
+ }
|
|
|
+ if (IsDropDownOpen)
|
|
|
+ {
|
|
|
+ RefreshView();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Method that handles the ObservableCollection.CollectionChanged event for the ItemsSource property.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sender">The object that raised the event.</param>
|
|
|
+ /// <param name="e">The event data.</param>
|
|
|
+ private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ // Update the cache
|
|
|
+ if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
|
|
|
+ {
|
|
|
+ for (int index = 0; index < e.OldItems.Count; index++)
|
|
|
+ {
|
|
|
+ _items.RemoveAt(e.OldStartingIndex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && _items.Count >= e.NewStartingIndex)
|
|
|
+ {
|
|
|
+ for (int index = 0; index < e.NewItems.Count; index++)
|
|
|
+ {
|
|
|
+ _items.Insert(e.NewStartingIndex + index, e.NewItems[index]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (e.Action == NotifyCollectionChangedAction.Replace && e.NewItems != null && e.OldItems != null)
|
|
|
+ {
|
|
|
+ for (int index = 0; index < e.NewItems.Count; index++)
|
|
|
+ {
|
|
|
+ _items[e.NewStartingIndex] = e.NewItems[index];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the view
|
|
|
+ if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace)
|
|
|
+ {
|
|
|
+ for (int index = 0; index < e.OldItems.Count; index++)
|
|
|
+ {
|
|
|
+ _view.Remove(e.OldItems[index]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e.Action == NotifyCollectionChangedAction.Reset)
|
|
|
+ {
|
|
|
+ // Significant changes to the underlying data.
|
|
|
+ ClearView();
|
|
|
+ if (Items != null)
|
|
|
+ {
|
|
|
+ _items = new List<object>(Items.Cast<object>().ToList());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Refresh the observable collection used in the selection adapter.
|
|
|
+ RefreshView();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Notifies the
|
|
|
+ /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> that the
|
|
|
+ /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Items" />
|
|
|
+ /// property has been set and the data can be filtered to provide
|
|
|
+ /// possible matches in the drop-down.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Call this method when you are providing custom population of
|
|
|
+ /// the drop-down portion of the AutoCompleteBox, to signal the control
|
|
|
+ /// that you are done with the population process.
|
|
|
+ /// Typically, you use PopulateComplete when the population process
|
|
|
+ /// is a long-running process and you want to cancel built-in filtering
|
|
|
+ /// of the ItemsSource items. In this case, you can handle the
|
|
|
+ /// Populated event and set PopulatingEventArgs.Cancel to true.
|
|
|
+ /// When the long-running process has completed you call
|
|
|
+ /// PopulateComplete to indicate the drop-down is populated.
|
|
|
+ /// </remarks>
|
|
|
+ public void PopulateComplete()
|
|
|
+ {
|
|
|
+ // Apply the search filter
|
|
|
+ RefreshView();
|
|
|
+
|
|
|
+ // Fire the Populated event containing the read-only view data.
|
|
|
+ PopulatedEventArgs populated = new PopulatedEventArgs(new ReadOnlyCollection<object>(_view));
|
|
|
+ OnPopulated(populated);
|
|
|
+
|
|
|
+ if (SelectionAdapter != null && SelectionAdapter.Items != _view)
|
|
|
+ {
|
|
|
+ SelectionAdapter.Items = _view;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool isDropDownOpen = _userCalledPopulate && (_view.Count > 0);
|
|
|
+ if (isDropDownOpen != IsDropDownOpen)
|
|
|
+ {
|
|
|
+ _ignorePropertyChange = true;
|
|
|
+ IsDropDownOpen = isDropDownOpen;
|
|
|
+ }
|
|
|
+ if (IsDropDownOpen)
|
|
|
+ {
|
|
|
+ OpeningDropDown(false);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ClosingDropDown(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ UpdateTextCompletion(_userCalledPopulate);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Performs text completion, if enabled, and a lookup on the underlying
|
|
|
+ /// item values for an exact match. Will update the SelectedItem value.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="userInitiated">A value indicating whether the operation
|
|
|
+ /// was user initiated. Text completion will not be performed when not
|
|
|
+ /// directly initiated by the user.</param>
|
|
|
+ private void UpdateTextCompletion(bool userInitiated)
|
|
|
+ {
|
|
|
+ // By default this method will clear the selected value
|
|
|
+ object newSelectedItem = null;
|
|
|
+ string text = Text;
|
|
|
+
|
|
|
+ // Text search is StartsWith explicit and only when enabled, in
|
|
|
+ // line with WPF's ComboBox lookup. When in use it will associate
|
|
|
+ // a Value with the Text if it is found in ItemsSource. This is
|
|
|
+ // only valid when there is data and the user initiated the action.
|
|
|
+ if (_view.Count > 0)
|
|
|
+ {
|
|
|
+ if (IsTextCompletionEnabled && TextBox != null && userInitiated)
|
|
|
+ {
|
|
|
+ int currentLength = TextBox.Text.Length;
|
|
|
+ int selectionStart = TextBoxSelectionStart;
|
|
|
+ if (selectionStart == text.Length && selectionStart > _textSelectionStart)
|
|
|
+ {
|
|
|
+ // When the FilterMode dependency property is set to
|
|
|
+ // either StartsWith or StartsWithCaseSensitive, the
|
|
|
+ // first item in the view is used. This will improve
|
|
|
+ // performance on the lookup. It assumes that the
|
|
|
+ // FilterMode the user has selected is an acceptable
|
|
|
+ // case sensitive matching function for their scenario.
|
|
|
+ object top = FilterMode == AutoCompleteFilterMode.StartsWith || FilterMode == AutoCompleteFilterMode.StartsWithCaseSensitive
|
|
|
+ ? _view[0]
|
|
|
+ : TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
|
|
|
+
|
|
|
+ // If the search was successful, update SelectedItem
|
|
|
+ if (top != null)
|
|
|
+ {
|
|
|
+ newSelectedItem = top;
|
|
|
+ string topString = FormatValue(top, true);
|
|
|
+
|
|
|
+ // Only replace partially when the two words being the same
|
|
|
+ int minLength = Math.Min(topString.Length, Text.Length);
|
|
|
+ if (AutoCompleteSearch.Equals(Text.Substring(0, minLength), topString.Substring(0, minLength)))
|
|
|
+ {
|
|
|
+ // Update the text
|
|
|
+ UpdateTextValue(topString);
|
|
|
+
|
|
|
+ // Select the text past the user's caret
|
|
|
+ TextBox.SelectionStart = currentLength;
|
|
|
+ TextBox.SelectionEnd = topString.Length;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Perform an exact string lookup for the text. This is a
|
|
|
+ // design change from the original Toolkit release when the
|
|
|
+ // IsTextCompletionEnabled property behaved just like the
|
|
|
+ // WPF ComboBox's IsTextSearchEnabled property.
|
|
|
+ //
|
|
|
+ // This change provides the behavior that most people expect
|
|
|
+ // to find: a lookup for the value is always performed.
|
|
|
+ newSelectedItem = TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the selected item property
|
|
|
+
|
|
|
+ if (SelectedItem != newSelectedItem)
|
|
|
+ {
|
|
|
+ _skipSelectedItemTextUpdate = true;
|
|
|
+ }
|
|
|
+ SelectedItem = newSelectedItem;
|
|
|
+
|
|
|
+ // Restore updates for TextSelection
|
|
|
+ if (_ignoreTextSelectionChange)
|
|
|
+ {
|
|
|
+ _ignoreTextSelectionChange = false;
|
|
|
+ if (TextBox != null)
|
|
|
+ {
|
|
|
+ _textSelectionStart = TextBoxSelectionStart;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Attempts to look through the view and locate the specific exact
|
|
|
+ /// text match.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="searchText">The search text.</param>
|
|
|
+ /// <param name="view">The view reference.</param>
|
|
|
+ /// <param name="predicate">The predicate to use for the partial or
|
|
|
+ /// exact match.</param>
|
|
|
+ /// <returns>Returns the object or null.</returns>
|
|
|
+ private object TryGetMatch(string searchText, AvaloniaList<object> view, AutoCompleteFilterPredicate<string> predicate)
|
|
|
+ {
|
|
|
+ if (view != null && view.Count > 0)
|
|
|
+ {
|
|
|
+ foreach (object o in view)
|
|
|
+ {
|
|
|
+ if (predicate(searchText, FormatValue(o)))
|
|
|
+ {
|
|
|
+ return o;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void UpdatePseudoClasses()
|
|
|
+ {
|
|
|
+ PseudoClasses.Set(":dropdownopen", IsDropDownOpen);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ClearTextBoxSelection()
|
|
|
+ {
|
|
|
+ if (TextBox != null)
|
|
|
+ {
|
|
|
+ int length = TextBox.Text?.Length ?? 0;
|
|
|
+ TextBox.SelectionStart = length;
|
|
|
+ TextBox.SelectionEnd = length;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Called when the selected item is changed, updates the text value
|
|
|
+ /// that is displayed in the text box part.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="newItem">The new item.</param>
|
|
|
+ private void OnSelectedItemChanged(object newItem)
|
|
|
+ {
|
|
|
+ string text;
|
|
|
+
|
|
|
+ if (newItem == null)
|
|
|
+ {
|
|
|
+ text = SearchText;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ text = FormatValue(newItem, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the Text property and the TextBox values
|
|
|
+ UpdateTextValue(text);
|
|
|
+
|
|
|
+ // Move the caret to the end of the text box
|
|
|
+ ClearTextBoxSelection();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handles the SelectionChanged event of the selection adapter.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sender">The source object.</param>
|
|
|
+ /// <param name="e">The selection changed event data.</param>
|
|
|
+ private void OnAdapterSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ SelectedItem = _adapter.SelectedItem;
|
|
|
+ }
|
|
|
+
|
|
|
+ //TODO Check UpdateTextCompletion
|
|
|
+ /// <summary>
|
|
|
+ /// Handles the Commit event on the selection adapter.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sender">The source object.</param>
|
|
|
+ /// <param name="e">The event data.</param>
|
|
|
+ private void OnAdapterSelectionComplete(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ IsDropDownOpen = false;
|
|
|
+
|
|
|
+ // Completion will update the selected value
|
|
|
+ //UpdateTextCompletion(false);
|
|
|
+
|
|
|
+ // Text should not be selected
|
|
|
+ ClearTextBoxSelection();
|
|
|
+
|
|
|
+ TextBox.Focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Handles the Cancel event on the selection adapter.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sender">The source object.</param>
|
|
|
+ /// <param name="e">The event data.</param>
|
|
|
+ private void OnAdapterSelectionCanceled(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ UpdateTextValue(SearchText);
|
|
|
+
|
|
|
+ // Completion will update the selected value
|
|
|
+ UpdateTextCompletion(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A predefined set of filter functions for the known, built-in
|
|
|
+ /// AutoCompleteFilterMode enumeration values.
|
|
|
+ /// </summary>
|
|
|
+ private static class AutoCompleteSearch
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Index function that retrieves the filter for the provided
|
|
|
+ /// AutoCompleteFilterMode.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="FilterMode">The built-in search mode.</param>
|
|
|
+ /// <returns>Returns the string-based comparison function.</returns>
|
|
|
+ public static AutoCompleteFilterPredicate<string> GetFilter(AutoCompleteFilterMode FilterMode)
|
|
|
+ {
|
|
|
+ switch (FilterMode)
|
|
|
+ {
|
|
|
+ case AutoCompleteFilterMode.Contains:
|
|
|
+ return Contains;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.ContainsCaseSensitive:
|
|
|
+ return ContainsCaseSensitive;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.ContainsOrdinal:
|
|
|
+ return ContainsOrdinal;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive:
|
|
|
+ return ContainsOrdinalCaseSensitive;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.Equals:
|
|
|
+ return Equals;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.EqualsCaseSensitive:
|
|
|
+ return EqualsCaseSensitive;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.EqualsOrdinal:
|
|
|
+ return EqualsOrdinal;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive:
|
|
|
+ return EqualsOrdinalCaseSensitive;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.StartsWith:
|
|
|
+ return StartsWith;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.StartsWithCaseSensitive:
|
|
|
+ return StartsWithCaseSensitive;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.StartsWithOrdinal:
|
|
|
+ return StartsWithOrdinal;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive:
|
|
|
+ return StartsWithOrdinalCaseSensitive;
|
|
|
+
|
|
|
+ case AutoCompleteFilterMode.None:
|
|
|
+ case AutoCompleteFilterMode.Custom:
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// An implementation of the Contains member of string that takes in a
|
|
|
+ /// string comparison. The traditional .NET string Contains member uses
|
|
|
+ /// StringComparison.Ordinal.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="s">The string.</param>
|
|
|
+ /// <param name="value">The string value to search for.</param>
|
|
|
+ /// <param name="comparison">The string comparison type.</param>
|
|
|
+ /// <returns>Returns true when the substring is found.</returns>
|
|
|
+ private static bool Contains(string s, string value, StringComparison comparison)
|
|
|
+ {
|
|
|
+ return s.IndexOf(value, comparison) >= 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the string value begins with the text.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool StartsWith(string text, string value)
|
|
|
+ {
|
|
|
+ return value.StartsWith(text, StringComparison.CurrentCultureIgnoreCase);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the string value begins with the text.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool StartsWithCaseSensitive(string text, string value)
|
|
|
+ {
|
|
|
+ return value.StartsWith(text, StringComparison.CurrentCulture);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the string value begins with the text.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool StartsWithOrdinal(string text, string value)
|
|
|
+ {
|
|
|
+ return value.StartsWith(text, StringComparison.OrdinalIgnoreCase);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the string value begins with the text.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool StartsWithOrdinalCaseSensitive(string text, string value)
|
|
|
+ {
|
|
|
+ return value.StartsWith(text, StringComparison.Ordinal);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the prefix is contained in the string value. The current
|
|
|
+ /// culture's case insensitive string comparison operator is used.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool Contains(string text, string value)
|
|
|
+ {
|
|
|
+ return Contains(value, text, StringComparison.CurrentCultureIgnoreCase);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the prefix is contained in the string value.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool ContainsCaseSensitive(string text, string value)
|
|
|
+ {
|
|
|
+ return Contains(value, text, StringComparison.CurrentCulture);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the prefix is contained in the string value.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool ContainsOrdinal(string text, string value)
|
|
|
+ {
|
|
|
+ return Contains(value, text, StringComparison.OrdinalIgnoreCase);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the prefix is contained in the string value.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool ContainsOrdinalCaseSensitive(string text, string value)
|
|
|
+ {
|
|
|
+ return Contains(value, text, StringComparison.Ordinal);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the string values are equal.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool Equals(string text, string value)
|
|
|
+ {
|
|
|
+ return value.Equals(text, StringComparison.CurrentCultureIgnoreCase);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the string values are equal.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool EqualsCaseSensitive(string text, string value)
|
|
|
+ {
|
|
|
+ return value.Equals(text, StringComparison.CurrentCulture);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the string values are equal.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool EqualsOrdinal(string text, string value)
|
|
|
+ {
|
|
|
+ return value.Equals(text, StringComparison.OrdinalIgnoreCase);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if the string values are equal.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="text">The AutoCompleteBox prefix text.</param>
|
|
|
+ /// <param name="value">The item's string value.</param>
|
|
|
+ /// <returns>Returns true if the condition is met.</returns>
|
|
|
+ public static bool EqualsOrdinalCaseSensitive(string text, string value)
|
|
|
+ {
|
|
|
+ return value.Equals(text, StringComparison.Ordinal);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A framework element that permits a binding to be evaluated in a new data
|
|
|
+ /// context leaf node.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">The type of dynamic binding to return.</typeparam>
|
|
|
+ public class BindingEvaluator<T> : Control
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the string value binding used by the control.
|
|
|
+ /// </summary>
|
|
|
+ private IBinding _binding;
|
|
|
+
|
|
|
+ #region public T Value
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Identifies the Value dependency property.
|
|
|
+ /// </summary>
|
|
|
+ public static readonly StyledProperty<T> ValueProperty =
|
|
|
+ AvaloniaProperty.Register<BindingEvaluator<T>, T>(nameof(Value));
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the data item value.
|
|
|
+ /// </summary>
|
|
|
+ public T Value
|
|
|
+ {
|
|
|
+ get { return GetValue(ValueProperty); }
|
|
|
+ set { SetValue(ValueProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion public string Value
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the value binding.
|
|
|
+ /// </summary>
|
|
|
+ public IBinding ValueBinding
|
|
|
+ {
|
|
|
+ get { return _binding; }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _binding = value;
|
|
|
+ AvaloniaObjectExtensions.Bind(this, ValueProperty, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of the BindingEvaluator class.
|
|
|
+ /// </summary>
|
|
|
+ public BindingEvaluator()
|
|
|
+ { }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of the BindingEvaluator class,
|
|
|
+ /// setting the initial binding to the provided parameter.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="binding">The initial string value binding.</param>
|
|
|
+ public BindingEvaluator(IBinding binding)
|
|
|
+ : this()
|
|
|
+ {
|
|
|
+ ValueBinding = binding;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Clears the data context so that the control does not keep a
|
|
|
+ /// reference to the last-looked up item.
|
|
|
+ /// </summary>
|
|
|
+ public void ClearDataContext()
|
|
|
+ {
|
|
|
+ DataContext = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Updates the data context of the framework element and returns the
|
|
|
+ /// updated binding value.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="o">The object to use as the data context.</param>
|
|
|
+ /// <param name="clearDataContext">If set to true, this parameter will
|
|
|
+ /// clear the data context immediately after retrieving the value.</param>
|
|
|
+ /// <returns>Returns the evaluated T value of the bound dependency
|
|
|
+ /// property.</returns>
|
|
|
+ public T GetDynamicValue(object o, bool clearDataContext)
|
|
|
+ {
|
|
|
+ DataContext = o;
|
|
|
+ T value = Value;
|
|
|
+ if (clearDataContext)
|
|
|
+ {
|
|
|
+ DataContext = null;
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Updates the data context of the framework element and returns the
|
|
|
+ /// updated binding value.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="o">The object to use as the data context.</param>
|
|
|
+ /// <returns>Returns the evaluated T value of the bound dependency
|
|
|
+ /// property.</returns>
|
|
|
+ public T GetDynamicValue(object o)
|
|
|
+ {
|
|
|
+ DataContext = o;
|
|
|
+ return Value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|