Merge pull request #768 from irihitech/multi-auto
New Control: MultiAutoCompleteBox
This commit is contained in:
@@ -0,0 +1,533 @@
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public partial class MultiAutoCompleteBox
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines see <see cref="TextBox.CaretIndex"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> CaretIndexProperty =
|
||||
TextBox.CaretIndexProperty.AddOwner<MultiAutoCompleteBox>(new(
|
||||
defaultValue: 0,
|
||||
defaultBindingMode:BindingMode.TwoWay));
|
||||
|
||||
public static readonly StyledProperty<string?> WatermarkProperty =
|
||||
TextBox.WatermarkProperty.AddOwner<MultiAutoCompleteBox>();
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="MinimumPrefixLength" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="MinimumPrefixLength" /> property.</value>
|
||||
public static readonly StyledProperty<int> MinimumPrefixLengthProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, int>(
|
||||
nameof(MinimumPrefixLength), validate: IsValidMinimumPrefixLength);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="MinimumPopulateDelay" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="MinimumPopulateDelay" /> property.</value>
|
||||
public static readonly StyledProperty<TimeSpan> MinimumPopulateDelayProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, TimeSpan>(
|
||||
nameof(MinimumPopulateDelay),
|
||||
TimeSpan.Zero,
|
||||
validate: IsValidMinimumPopulateDelay);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="MaxDropDownHeight" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="MaxDropDownHeight" /> property.</value>
|
||||
public static readonly StyledProperty<double> MaxDropDownHeightProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, double>(
|
||||
nameof(MaxDropDownHeight),
|
||||
double.PositiveInfinity,
|
||||
validate: IsValidMaxDropDownHeight);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="IsTextCompletionEnabled" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="IsTextCompletionEnabled" /> property.</value>
|
||||
public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, bool>(
|
||||
nameof(IsTextCompletionEnabled));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ItemTemplate" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="ItemTemplate" /> property.</value>
|
||||
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, IDataTemplate>(
|
||||
nameof(ItemTemplate));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="IsDropDownOpen" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="IsDropDownOpen" /> property.</value>
|
||||
public static readonly StyledProperty<bool> IsDropDownOpenProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, bool>(
|
||||
nameof(IsDropDownOpen));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="Text" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="Text" /> property.</value>
|
||||
public static readonly StyledProperty<string?> TextProperty =
|
||||
TextBlock.TextProperty.AddOwner<MultiAutoCompleteBox>(new(string.Empty,
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
enableDataValidation: true));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="SearchText" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="SearchText" /> property.</value>
|
||||
public static readonly DirectProperty<MultiAutoCompleteBox, string?> SearchTextProperty =
|
||||
AvaloniaProperty.RegisterDirect<MultiAutoCompleteBox, string?>(
|
||||
nameof(SearchText),
|
||||
o => o.SearchText,
|
||||
unsetValue: string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier for the <see cref="FilterMode" /> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<AutoCompleteFilterMode> FilterModeProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, AutoCompleteFilterMode>(
|
||||
nameof(FilterMode),
|
||||
defaultValue: AutoCompleteFilterMode.StartsWith,
|
||||
validate: IsValidFilterMode);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ItemFilter" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="ItemFilter" /> property.</value>
|
||||
public static readonly StyledProperty<AutoCompleteFilterPredicate<object?>?> ItemFilterProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, AutoCompleteFilterPredicate<object?>?>(
|
||||
nameof(ItemFilter));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="TextFilter" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="TextFilter" /> property.</value>
|
||||
public static readonly StyledProperty<AutoCompleteFilterPredicate<string?>?> TextFilterProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, AutoCompleteFilterPredicate<string?>?>(
|
||||
nameof(TextFilter),
|
||||
defaultValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ItemSelector" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="ItemSelector" /> property.</value>
|
||||
public static readonly StyledProperty<AutoCompleteSelector<object>?> ItemSelectorProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, AutoCompleteSelector<object>?>(
|
||||
nameof(ItemSelector));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="TextSelector" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="TextSelector" /> property.</value>
|
||||
public static readonly StyledProperty<AutoCompleteSelector<string?>?> TextSelectorProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, AutoCompleteSelector<string?>?>(
|
||||
nameof(TextSelector));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ItemsSource" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="ItemsSource" /> property.</value>
|
||||
public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, IEnumerable?>(
|
||||
nameof(ItemsSource));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="AsyncPopulator" /> property.
|
||||
/// </summary>
|
||||
/// <value>The identifier for the <see cref="AsyncPopulator" /> property.</value>
|
||||
public static readonly StyledProperty<Func<string?, CancellationToken, Task<IEnumerable<object>>>?> AsyncPopulatorProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?>(
|
||||
nameof(AsyncPopulator));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MaxLength"/> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> MaxLengthProperty =
|
||||
TextBox.MaxLengthProperty.AddOwner<MultiAutoCompleteBox>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="InnerLeftContent"/> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<object?> InnerLeftContentProperty =
|
||||
TextBox.InnerLeftContentProperty.AddOwner<MultiAutoCompleteBox>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="InnerRightContent"/> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<object?> InnerRightContentProperty =
|
||||
TextBox.InnerRightContentProperty.AddOwner<MultiAutoCompleteBox>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SelectedItems"/> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<IList?> SelectedItemsProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, IList?>(
|
||||
nameof(SelectedItems));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected items. It is recommended to use an <see cref="ObservableCollection{T}"/>.
|
||||
/// This property must be initialized from ViewModel.
|
||||
/// </summary>
|
||||
public IList? SelectedItems
|
||||
{
|
||||
get => GetValue(SelectedItemsProperty);
|
||||
set => SetValue(SelectedItemsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="SelectedItemTemplate" /> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<IDataTemplate?> SelectedItemTemplateProperty =
|
||||
AvaloniaProperty.Register<MultiAutoCompleteBox, IDataTemplate?>(nameof(SelectedItemTemplate));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="T:Avalonia.DataTemplate" /> used to display each item in SelectedItems.
|
||||
/// </summary>
|
||||
[InheritDataTypeFromItems(nameof(SelectedItems))]
|
||||
public IDataTemplate? SelectedItemTemplate
|
||||
{
|
||||
get => GetValue(SelectedItemTemplateProperty);
|
||||
set => SetValue(SelectedItemTemplateProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the caret index
|
||||
/// </summary>
|
||||
public int CaretIndex
|
||||
{
|
||||
get => GetValue(CaretIndexProperty);
|
||||
set => SetValue(CaretIndexProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum number of characters required to be entered
|
||||
/// in the text box before the <see cref="AutoCompleteBox" /> displays possible matches.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The minimum number of characters to be entered in the text box
|
||||
/// before the <see cref="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 => 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 => 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 => 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="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="AutoCompleteBox" /> populates
|
||||
/// the list of possible matches in the drop-down. The default is 0.</value>
|
||||
public TimeSpan MinimumPopulateDelay
|
||||
{
|
||||
get => GetValue(MinimumPopulateDelayProperty);
|
||||
set => SetValue(MinimumPopulateDelayProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum height of the drop-down portion of the
|
||||
/// <see cref="AutoCompleteBox" /> control.
|
||||
/// </summary>
|
||||
/// <value>The maximum height of the drop-down portion of the
|
||||
/// <see cref="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 => 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 => GetValue(IsDropDownOpenProperty);
|
||||
set => SetValue(IsDropDownOpenProperty, 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="AutoCompleteBox" />
|
||||
/// control.
|
||||
/// </summary>
|
||||
/// <value>The <see cref="T:Avalonia.Data.IBinding" /> object used
|
||||
/// when binding to a collection property.</value>
|
||||
[AssignBinding]
|
||||
[InheritDataTypeFromItems(nameof(ItemsSource))]
|
||||
public IBinding? ValueMemberBinding
|
||||
{
|
||||
get => _valueBindingEvaluator?.ValueBinding;
|
||||
set
|
||||
{
|
||||
if (ValueMemberBinding != value)
|
||||
{
|
||||
_valueBindingEvaluator = new BindingEvaluator<string>(value);
|
||||
OnValueMemberBindingChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text in the text box portion of the
|
||||
/// <see cref="AutoCompleteBox" /> control.
|
||||
/// </summary>
|
||||
/// <value>The text in the text box portion of the
|
||||
/// <see cref="AutoCompleteBox" /> control.</value>
|
||||
public string? Text
|
||||
{
|
||||
get => GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text that is used to filter items in the
|
||||
/// <see cref="ItemsSource" /> item collection.
|
||||
/// </summary>
|
||||
/// <value>The text that is used to filter items in the
|
||||
/// <see cref="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 => _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="ItemsSource" />
|
||||
/// property for display in the drop-down.
|
||||
/// </summary>
|
||||
/// <value>One of the <see cref="AutoCompleteFilterMode" />
|
||||
/// values The default is <see cref="AutoCompleteFilterMode.StartsWith" />.</value>
|
||||
/// <exception cref="T:System.ArgumentException">The specified value is not a valid
|
||||
/// <see cref="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 => GetValue(FilterModeProperty);
|
||||
set => SetValue(FilterModeProperty, value);
|
||||
}
|
||||
|
||||
public string? Watermark
|
||||
{
|
||||
get => 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="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="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 => GetValue(ItemFilterProperty);
|
||||
set => SetValue(ItemFilterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom method that uses the user-entered text to
|
||||
/// filter items specified by the <see cref="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="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 => GetValue(TextFilterProperty);
|
||||
set => SetValue(TextFilterProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom method that combines the user-entered
|
||||
/// text and one of the items specified by the <see cref="ItemsSource" />.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The custom method that combines the user-entered
|
||||
/// text and one of the items specified by the <see cref="ItemsSource" />.
|
||||
/// </value>
|
||||
public AutoCompleteSelector<object>? ItemSelector
|
||||
{
|
||||
get => GetValue(ItemSelectorProperty);
|
||||
set => SetValue(ItemSelectorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom method that combines the user-entered
|
||||
/// text and one of the items specified by the
|
||||
/// <see cref="ItemsSource" /> in a text-based way.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The custom method that combines the user-entered
|
||||
/// text and one of the items specified by the <see cref="ItemsSource" />
|
||||
/// in a text-based way.
|
||||
/// </value>
|
||||
public AutoCompleteSelector<string?>? TextSelector
|
||||
{
|
||||
get => GetValue(TextSelectorProperty);
|
||||
set => SetValue(TextSelectorProperty, value);
|
||||
}
|
||||
|
||||
public Func<string?, CancellationToken, Task<IEnumerable<object>>>? AsyncPopulator
|
||||
{
|
||||
get => GetValue(AsyncPopulatorProperty);
|
||||
set => SetValue(AsyncPopulatorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection that is used to generate the items for the
|
||||
/// drop-down portion of the <see cref="AutoCompleteBox" /> control.
|
||||
/// </summary>
|
||||
/// <value>The collection that is used to generate the items of the
|
||||
/// drop-down portion of the <see cref="AutoCompleteBox" /> control.</value>
|
||||
public IEnumerable? ItemsSource
|
||||
{
|
||||
get => GetValue(ItemsSourceProperty);
|
||||
set => SetValue(ItemsSourceProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of characters that the <see cref="AutoCompleteBox"/> can accept.
|
||||
/// This constraint only applies for manually entered (user-inputted) text.
|
||||
/// </summary>
|
||||
public int MaxLength
|
||||
{
|
||||
get => GetValue(MaxLengthProperty);
|
||||
set => SetValue(MaxLengthProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets custom content that is positioned on the left side of the text layout box
|
||||
/// </summary>
|
||||
public object? InnerLeftContent
|
||||
{
|
||||
get => GetValue(InnerLeftContentProperty);
|
||||
set => SetValue(InnerLeftContentProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets custom content that is positioned on the right side of the text layout box
|
||||
/// </summary>
|
||||
public object? InnerRightContent
|
||||
{
|
||||
get => GetValue(InnerRightContentProperty);
|
||||
set => SetValue(InnerRightContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<object?> PopupInnerTopContentProperty = AvaloniaProperty.Register<MultiAutoCompleteBox, object?>(
|
||||
nameof(PopupInnerTopContent));
|
||||
|
||||
public object? PopupInnerTopContent
|
||||
{
|
||||
get => GetValue(PopupInnerTopContentProperty);
|
||||
set => SetValue(PopupInnerTopContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<object?> PopupInnerBottomContentProperty = AvaloniaProperty.Register<MultiAutoCompleteBox, object?>(
|
||||
nameof(PopupInnerBottomContent));
|
||||
|
||||
public object? PopupInnerBottomContent
|
||||
{
|
||||
get => GetValue(PopupInnerBottomContentProperty);
|
||||
set => SetValue(PopupInnerBottomContentProperty, value);
|
||||
}
|
||||
}
|
||||
1947
src/Ursa/Controls/AutoCompleteBox/MultiAutoCompleteBox.cs
Normal file
1947
src/Ursa/Controls/AutoCompleteBox/MultiAutoCompleteBox.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,299 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Utils;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.LogicalTree;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class MultiAutoCompleteSelectionAdapter : ISelectionAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// The SelectingItemsControl instance.
|
||||
/// </summary>
|
||||
private SelectingItemsControl? _selector;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the
|
||||
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />
|
||||
/// class.
|
||||
/// </summary>
|
||||
public MultiAutoCompleteSelectionAdapter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the
|
||||
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapterr" />
|
||||
/// class with the specified
|
||||
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
|
||||
/// control.
|
||||
/// </summary>
|
||||
/// <param name="selector">
|
||||
/// The
|
||||
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" /> control
|
||||
/// to wrap as a
|
||||
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />.
|
||||
/// </param>
|
||||
public MultiAutoCompleteSelectionAdapter(SelectingItemsControl selector)
|
||||
{
|
||||
SelectorControl = selector;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the selection change event
|
||||
/// should not be fired.
|
||||
/// </summary>
|
||||
private bool IgnoringSelectionChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying
|
||||
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
|
||||
/// control.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The underlying
|
||||
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
|
||||
/// control.
|
||||
/// </value>
|
||||
public SelectingItemsControl? SelectorControl
|
||||
{
|
||||
get => _selector;
|
||||
|
||||
set
|
||||
{
|
||||
if (_selector != null)
|
||||
{
|
||||
_selector.SelectionChanged -= OnSelectionChanged;
|
||||
_selector.PointerReleased -= OnSelectorPointerReleased;
|
||||
}
|
||||
|
||||
_selector = value;
|
||||
|
||||
if (_selector != null)
|
||||
{
|
||||
_selector.SelectionChanged += OnSelectionChanged;
|
||||
_selector.PointerReleased += OnSelectorPointerReleased;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the
|
||||
/// <see cref="P:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.SelectedItem" />
|
||||
/// property value changes.
|
||||
/// </summary>
|
||||
public event EventHandler<SelectionChangedEventArgs>? SelectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when an item is selected and is committed to the underlying
|
||||
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
|
||||
/// control.
|
||||
/// </summary>
|
||||
public event EventHandler<RoutedEventArgs>? Commit;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a selection is canceled before it is committed.
|
||||
/// </summary>
|
||||
public event EventHandler<RoutedEventArgs>? Cancel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected item of the selection adapter.
|
||||
/// </summary>
|
||||
/// <value>The selected item of the underlying selection adapter.</value>
|
||||
public object? SelectedItem
|
||||
{
|
||||
get => SelectorControl?.SelectedItem;
|
||||
|
||||
set
|
||||
{
|
||||
IgnoringSelectionChanged = true;
|
||||
if (SelectorControl != null)
|
||||
{
|
||||
SelectorControl.SelectedItem = value;
|
||||
}
|
||||
// Attempt to reset the scroll viewer's position
|
||||
if (value == null) ResetScrollViewer();
|
||||
IgnoringSelectionChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection that is used to generate the content of
|
||||
/// the selection adapter.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The collection used to generate content for the selection
|
||||
/// adapter.
|
||||
/// </value>
|
||||
public IEnumerable? ItemsSource
|
||||
{
|
||||
get => SelectorControl?.ItemsSource;
|
||||
set
|
||||
{
|
||||
if (SelectorControl != null) SelectorControl.ItemsSource = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides handling for the
|
||||
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
|
||||
/// when a key is pressed while the drop-down portion of the
|
||||
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
|
||||
/// </summary>
|
||||
/// <param name="e">
|
||||
/// A <see cref="T:Avalonia.Input.KeyEventArgs" />
|
||||
/// that contains data about the
|
||||
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.
|
||||
/// </param>
|
||||
public void HandleKeyDown(KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter:
|
||||
OnCommit();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.Up:
|
||||
SelectedIndexDecrement();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.Down:
|
||||
if ((e.KeyModifiers & KeyModifiers.Alt) == KeyModifiers.None)
|
||||
{
|
||||
SelectedIndexIncrement();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Key.Escape:
|
||||
OnCancel();
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the control contains a ScrollViewer, this will reset the viewer
|
||||
/// to be scrolled to the top.
|
||||
/// </summary>
|
||||
private void ResetScrollViewer()
|
||||
{
|
||||
if (SelectorControl != null)
|
||||
{
|
||||
var sv = SelectorControl.GetLogicalDescendants().OfType<ScrollViewer>().FirstOrDefault();
|
||||
if (sv != null) sv.Offset = new Vector(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the mouse left button up event on the selector control.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source object.</param>
|
||||
/// <param name="e">The event data.</param>
|
||||
private void OnSelectorPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (e.InitialPressMouseButton == MouseButton.Left) OnCommit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the SelectionChanged event on the SelectingItemsControl control.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source object.</param>
|
||||
/// <param name="e">The selection changed event data.</param>
|
||||
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (IgnoringSelectionChanged) return;
|
||||
// SelectedItem = SelectorControl?.SelectedItem;
|
||||
SelectionChanged?.Invoke(this, e);
|
||||
// _previewSelectedItem = SelectorControl?.SelectedItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the
|
||||
/// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
|
||||
/// property of the underlying
|
||||
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
|
||||
/// control.
|
||||
/// </summary>
|
||||
protected void SelectedIndexIncrement()
|
||||
{
|
||||
if (SelectorControl != null)
|
||||
SelectorControl.SelectedIndex = SelectorControl.SelectedIndex + 1 >= SelectorControl.ItemCount
|
||||
? -1
|
||||
: SelectorControl.SelectedIndex + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the
|
||||
/// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
|
||||
/// property of the underlying
|
||||
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
|
||||
/// control.
|
||||
/// </summary>
|
||||
protected void SelectedIndexDecrement()
|
||||
{
|
||||
if (SelectorControl != null)
|
||||
{
|
||||
var index = SelectorControl.SelectedIndex;
|
||||
if (index >= 0)
|
||||
SelectorControl.SelectedIndex--;
|
||||
else if (index == -1) SelectorControl.SelectedIndex = SelectorControl.ItemCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the
|
||||
/// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Commit" />
|
||||
/// event.
|
||||
/// </summary>
|
||||
internal void OnCommit()
|
||||
{
|
||||
/*
|
||||
if (_previewSelectedItem is null) return;
|
||||
SelectedItem = _previewSelectedItem;
|
||||
SelectionChanged?.Invoke(this,
|
||||
new SelectionChangedEventArgs(
|
||||
SelectingItemsControl.SelectionChangedEvent,
|
||||
new List<object?>(),
|
||||
new List<object?> { SelectedItem }
|
||||
)
|
||||
);
|
||||
*/
|
||||
Commit?.Invoke(this, new RoutedEventArgs());
|
||||
AfterAdapterAction();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the
|
||||
/// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Cancel" />
|
||||
/// event.
|
||||
/// </summary>
|
||||
private void OnCancel()
|
||||
{
|
||||
Cancel?.Invoke(this, new RoutedEventArgs());
|
||||
AfterAdapterAction();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the selection after the actions are complete.
|
||||
/// </summary>
|
||||
private void AfterAdapterAction()
|
||||
{
|
||||
IgnoringSelectionChanged = true;
|
||||
if (SelectorControl != null)
|
||||
{
|
||||
SelectorControl.SelectedItem = null;
|
||||
SelectorControl.SelectedIndex = -1;
|
||||
}
|
||||
IgnoringSelectionChanged = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user