Merge pull request #768 from irihitech/multi-auto
New Control: MultiAutoCompleteBox
This commit is contained in:
@@ -23,10 +23,14 @@
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
|
||||
<TextBlock Text="{Binding #box.((viewModels:StateData)SelectedItem).Name}" />
|
||||
|
||||
<u:AutoCompleteBox
|
||||
Name="box"
|
||||
Watermark="Please select a State"
|
||||
Classes="ClearButton"
|
||||
ValueMemberBinding="{ReflectionBinding Name}" />
|
||||
|
||||
<u:AutoCompleteBox
|
||||
Classes="Large"
|
||||
ValueMemberBinding="{ReflectionBinding Name}" />
|
||||
|
||||
33
demo/Ursa.Demo/Pages/MultiAutoCompleteBoxDemo.axaml
Normal file
33
demo/Ursa.Demo/Pages/MultiAutoCompleteBoxDemo.axaml
Normal file
@@ -0,0 +1,33 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:vm="clr-namespace:Ursa.Demo.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:DataType="vm:MultiAutoCompleteBoxDemoViewModel"
|
||||
x:Class="Ursa.Demo.Pages.MultiAutoCompleteBoxDemo">
|
||||
<StackPanel Spacing="20" HorizontalAlignment="Left">
|
||||
<TextBlock Text="Multi-AutoCompleteBox"/>
|
||||
<u:MultiAutoCompleteBox ItemsSource="{Binding Items}"
|
||||
MaxWidth="400"
|
||||
InnerLeftContent="Controls"
|
||||
SelectedItems="{Binding SelectedItems}"
|
||||
ItemFilter="{Binding FilterPredicate}"
|
||||
FilterMode="Custom">
|
||||
<u:MultiAutoCompleteBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:ControlData">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding MenuHeader}" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding Chinese}" Classes="Secondary" FontSize="12" Margin="8 0 0 0" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:MultiAutoCompleteBox.ItemTemplate>
|
||||
<u:MultiAutoCompleteBox.SelectedItemTemplate>
|
||||
<DataTemplate DataType="vm:ControlData">
|
||||
<TextBlock Text="{Binding MenuHeader}" />
|
||||
</DataTemplate>
|
||||
</u:MultiAutoCompleteBox.SelectedItemTemplate>
|
||||
</u:MultiAutoCompleteBox>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
13
demo/Ursa.Demo/Pages/MultiAutoCompleteBoxDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/MultiAutoCompleteBoxDemo.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Ursa.Demo.Pages;
|
||||
|
||||
public partial class MultiAutoCompleteBoxDemo : UserControl
|
||||
{
|
||||
public MultiAutoCompleteBoxDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@ public partial class MainViewViewModel : ViewModelBase
|
||||
MenuKeys.MenuKeyAspectRatioLayout => new AspectRatioLayoutDemoViewModel(),
|
||||
MenuKeys.MenuKeyPathPicker => new PathPickerDemoViewModel(),
|
||||
MenuKeys.MenuKeyAnchor => new AnchorDemoViewModel(),
|
||||
MenuKeys.MenuKeyMultiAutoCompleteBox => new MultiAutoCompleteBoxDemoViewModel(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(s), s, null)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ public class MenuViewModel : ViewModelBase
|
||||
new() { MenuHeader = "KeyGestureInput", Key = MenuKeys.MenuKeyKeyGestureInput },
|
||||
new() { MenuHeader = "IPv4Box", Key = MenuKeys.MenuKeyIpBox },
|
||||
new() { MenuHeader = "MultiComboBox", Key = MenuKeys.MenuKeyMultiComboBox },
|
||||
new() { MenuHeader = "Multi AutoCompleteBox", Key = MenuKeys.MenuKeyMultiAutoCompleteBox, Status = "New" },
|
||||
new() { MenuHeader = "Numeric UpDown", Key = MenuKeys.MenuKeyNumericUpDown },
|
||||
new() { MenuHeader = "NumPad", Key = MenuKeys.MenuKeyNumPad },
|
||||
new() { MenuHeader = "PathPicker", Key = MenuKeys.MenuKeyPathPicker, Status = "New" },
|
||||
@@ -158,4 +159,5 @@ public static class MenuKeys
|
||||
public const string MenuKeyAspectRatioLayout = "AspectRatioLayout";
|
||||
public const string MenuKeyPathPicker = "PathPicker";
|
||||
public const string MenuKeyAnchor = "Anchor";
|
||||
public const string MenuKeyMultiAutoCompleteBox = "MultiAutoCompleteBox";
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Ursa.Demo.ViewModels;
|
||||
|
||||
public class MultiAutoCompleteBoxDemoViewModel : ObservableObject
|
||||
{
|
||||
public ObservableCollection<ControlData> Items { get; set; }
|
||||
public ObservableCollection<ControlData> SelectedItems { get; set; }
|
||||
public AutoCompleteFilterPredicate<object> FilterPredicate { get; set; }
|
||||
|
||||
public MultiAutoCompleteBoxDemoViewModel()
|
||||
{
|
||||
SelectedItems = new ObservableCollection<ControlData>();
|
||||
Items = new ObservableCollection<ControlData>
|
||||
{
|
||||
new() { MenuHeader = "Button Group", Chinese = "按钮组" },
|
||||
new() { MenuHeader = "Icon Button", Chinese = "图标按钮" },
|
||||
new() { MenuHeader = "AutoCompleteBox", Chinese = "自动完成框" },
|
||||
new() { MenuHeader = "Class Input", Chinese = "类输入框" },
|
||||
new() { MenuHeader = "Enum Selector", Chinese = "枚举选择器" },
|
||||
new() { MenuHeader = "Form", Chinese = "表单" },
|
||||
new() { MenuHeader = "KeyGestureInput", Chinese = "快捷键输入" },
|
||||
new() { MenuHeader = "IPv4Box", Chinese = "IPv4输入框" },
|
||||
new() { MenuHeader = "MultiComboBox", Chinese = "多选组合框" },
|
||||
new() { MenuHeader = "Multi AutoCompleteBox", Chinese = "多项自动完成框" },
|
||||
new() { MenuHeader = "Numeric UpDown", Chinese = "数字上下调节" },
|
||||
new() { MenuHeader = "NumPad", Chinese = "数字键盘" },
|
||||
new() { MenuHeader = "PathPicker", Chinese = "路径选择器" },
|
||||
new() { MenuHeader = "PinCode", Chinese = "密码输入" },
|
||||
new() { MenuHeader = "RangeSlider", Chinese = "范围滑块" },
|
||||
new() { MenuHeader = "Rating", Chinese = "评分" },
|
||||
new() { MenuHeader = "Selection List", Chinese = "选择列表" },
|
||||
new() { MenuHeader = "TagInput", Chinese = "标签输入" },
|
||||
new() { MenuHeader = "Theme Toggler", Chinese = "主题切换" },
|
||||
new() { MenuHeader = "TreeComboBox", Chinese = "树形组合框" },
|
||||
|
||||
new() { MenuHeader = "Dialog", Chinese = "对话框" },
|
||||
new() { MenuHeader = "Drawer", Chinese = "抽屉" },
|
||||
new() { MenuHeader = "Loading", Chinese = "加载" },
|
||||
new() { MenuHeader = "Message Box", Chinese = "消息框" },
|
||||
new() { MenuHeader = "Notification", Chinese = "通知" },
|
||||
new() { MenuHeader = "PopConfirm", Chinese = "气泡确认" },
|
||||
new() { MenuHeader = "Toast", Chinese = "吐司" },
|
||||
new() { MenuHeader = "Skeleton", Chinese = "骨架屏" },
|
||||
|
||||
new() { MenuHeader = "Date Picker", Chinese = "日期选择器" },
|
||||
new() { MenuHeader = "Date Range Picker", Chinese = "日期范围选择器" },
|
||||
new() { MenuHeader = "Date Time Picker", Chinese = "日期时间选择器" },
|
||||
new() { MenuHeader = "Time Box", Chinese = "时间输入框" },
|
||||
new() { MenuHeader = "Time Picker", Chinese = "时间选择器" },
|
||||
new() { MenuHeader = "Time Range Picker", Chinese = "时间范围选择器" },
|
||||
new() { MenuHeader = "Clock", Chinese = "时钟" },
|
||||
|
||||
new() { MenuHeader = "Anchor", Chinese = "锚点" },
|
||||
new() { MenuHeader = "Breadcrumb", Chinese = "面包屑" },
|
||||
new() { MenuHeader = "Nav Menu", Chinese = "导航菜单" },
|
||||
new() { MenuHeader = "Pagination", Chinese = "分页" },
|
||||
new() { MenuHeader = "ToolBar", Chinese = "工具栏" },
|
||||
|
||||
new() { MenuHeader = "AspectRatioLayout", Chinese = "宽高比布局" },
|
||||
new() { MenuHeader = "Avatar", Chinese = "头像" },
|
||||
new() { MenuHeader = "Badge", Chinese = "徽章" },
|
||||
new() { MenuHeader = "Banner", Chinese = "横幅" },
|
||||
new() { MenuHeader = "Disable Container", Chinese = "禁用容器" },
|
||||
new() { MenuHeader = "Divider", Chinese = "分割线" },
|
||||
new() { MenuHeader = "DualBadge", Chinese = "双徽章" },
|
||||
new() { MenuHeader = "ImageViewer", Chinese = "图片查看器" },
|
||||
new() { MenuHeader = "ElasticWrapPanel", Chinese = "弹性换行面板" },
|
||||
new() { MenuHeader = "Marquee", Chinese = "跑马灯" },
|
||||
new() { MenuHeader = "Number Displayer", Chinese = "数字显示器" },
|
||||
new() { MenuHeader = "Scroll To", Chinese = "滚动到按钮" },
|
||||
new() { MenuHeader = "Timeline", Chinese = "时间轴" },
|
||||
new() { MenuHeader = "TwoTonePathIcon", Chinese = "双色路径图标" }
|
||||
};
|
||||
FilterPredicate = Search;
|
||||
}
|
||||
|
||||
private static bool Search(string? text, object? data)
|
||||
{
|
||||
if (text is null) return true;
|
||||
if (data is not ControlData control) return false;
|
||||
return control.MenuHeader.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
control.Chinese.Contains(text, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public class ControlData
|
||||
{
|
||||
public required string MenuHeader { get; init; }
|
||||
public required string Chinese { get; init; }
|
||||
}
|
||||
87
src/Ursa.Themes.Semi/Controls/MultiAutoCompleteBox.axaml
Normal file
87
src/Ursa.Themes.Semi/Controls/MultiAutoCompleteBox.axaml
Normal file
@@ -0,0 +1,87 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="https://irihi.tech/ursa">
|
||||
<ControlTheme x:Key="{x:Type u:MultiAutoCompleteBox}" TargetType="u:MultiAutoCompleteBox">
|
||||
<Setter Property="MinHeight" Value="{DynamicResource AutoCompleteBoxDefaultHeight}" />
|
||||
<Setter Property="MaxDropDownHeight" Value="{DynamicResource AutoCompleteMaxDropdownHeight}" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource TextBoxDefaultCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="u:MultiAutoCompleteBox">
|
||||
<Panel>
|
||||
<Border
|
||||
Name="PART_RootBorder"
|
||||
MinHeight="30"
|
||||
VerticalAlignment="Stretch"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Background="{DynamicResource TextBoxDefaultBackground}"
|
||||
BorderBrush="{DynamicResource TextBoxDefaultBorderBrush}">
|
||||
<Grid ColumnDefinitions="Auto, *, Auto">
|
||||
<ContentPresenter
|
||||
Grid.Column="0"
|
||||
Margin="8,0"
|
||||
IsHitTestVisible="False"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding InnerLeftContent}"
|
||||
Foreground="{DynamicResource TextBoxInnerForeground}"
|
||||
IsVisible="{TemplateBinding InnerLeftContent,
|
||||
Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
<u:MultiComboBoxSelectedItemList
|
||||
Grid.Column="1"
|
||||
Name="{x:Static u:MultiAutoCompleteBox.PART_SelectedItemsControl}"
|
||||
VerticalAlignment="Center"
|
||||
RemoveCommand="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Remove}"
|
||||
ItemTemplate="{TemplateBinding SelectedItemTemplate}"
|
||||
ItemsSource="{TemplateBinding SelectedItems}" >
|
||||
<u:MultiComboBoxSelectedItemList.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<u:WrapPanelWithTrailingItem>
|
||||
<u:WrapPanelWithTrailingItem.TrailingItem>
|
||||
<TextBox VerticalAlignment="Center" MinWidth="24" Theme="{DynamicResource TagInputTextBoxTheme}"/>
|
||||
</u:WrapPanelWithTrailingItem.TrailingItem>
|
||||
</u:WrapPanelWithTrailingItem>
|
||||
</ItemsPanelTemplate>
|
||||
</u:MultiComboBoxSelectedItemList.ItemsPanel>
|
||||
</u:MultiComboBoxSelectedItemList>
|
||||
<ContentPresenter
|
||||
Grid.Column="2"
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
Content="{TemplateBinding InnerRightContent}"
|
||||
Foreground="{DynamicResource TextBoxInnerForeground}"
|
||||
IsVisible="{TemplateBinding InnerRightContent,
|
||||
Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Popup
|
||||
Name="PART_Popup"
|
||||
MaxHeight="{TemplateBinding MaxDropDownHeight}"
|
||||
IsLightDismissEnabled="True"
|
||||
PlacementTarget="{TemplateBinding}">
|
||||
<Border
|
||||
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Margin="{DynamicResource AutoCompleteBoxPopupMargin}"
|
||||
Padding="{DynamicResource AutoCompleteBoxPopupPadding}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource AutoCompleteBoxPopupBackground}"
|
||||
BorderBrush="{DynamicResource AutoCompleteBoxPopupBorderBrush}"
|
||||
BorderThickness="{DynamicResource AutoCompleteBoxPopupBorderThickness}"
|
||||
BoxShadow="{DynamicResource AutoCompleteBoxPopupBoxShadow}"
|
||||
CornerRadius="{DynamicResource AutoCompleteBoxPopupCornerRadius}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<ContentPresenter Content="{TemplateBinding PopupInnerTopContent}" DockPanel.Dock="Top" />
|
||||
<ContentPresenter Content="{TemplateBinding PopupInnerBottomContent}" DockPanel.Dock="Bottom" />
|
||||
<ListBox
|
||||
Name="PART_SelectingItemsControl"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
ItemTemplate="{TemplateBinding ItemTemplate}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -63,7 +63,7 @@
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="TagInputTextBoxTheme" TargetType="TextBox">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBoxInnerForeground}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBoxForeground}" />
|
||||
<Setter Property="Background" Value="{DynamicResource TextBoxDefaultBackground}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource TextBoxDefaultBorderBrush}" />
|
||||
<Setter Property="SelectionBrush" Value="{DynamicResource TextBoxSelectionBackground}" />
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<ResourceInclude Source="Loading.axaml" />
|
||||
<ResourceInclude Source="Marquee.axaml" />
|
||||
<ResourceInclude Source="MessageBox.axaml" />
|
||||
<ResourceInclude Source="MultiAutoCompleteBox.axaml" />
|
||||
<ResourceInclude Source="MultiComboBox.axaml" />
|
||||
<ResourceInclude Source="NavMenu.axaml" />
|
||||
<ResourceInclude Source="Notification.axaml" />
|
||||
|
||||
38
src/Ursa/Common/ObservableHelper.cs
Normal file
38
src/Ursa/Common/ObservableHelper.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Avalonia.Reactive;
|
||||
|
||||
namespace Ursa.Common;
|
||||
|
||||
internal static class ObservableHelper
|
||||
{
|
||||
public static IObservable<T> Skip<T>(this IObservable<T> source, int skipCount)
|
||||
{
|
||||
if (skipCount <= 0) throw new ArgumentException("Skip count must be bigger than zero", nameof(skipCount));
|
||||
|
||||
return Create<T>(obs =>
|
||||
{
|
||||
var remaining = skipCount;
|
||||
return source.Subscribe(new AnonymousObserver<T>(
|
||||
input =>
|
||||
{
|
||||
if (remaining <= 0)
|
||||
obs.OnNext(input);
|
||||
else
|
||||
remaining--;
|
||||
}, obs.OnError, obs.OnCompleted));
|
||||
});
|
||||
}
|
||||
|
||||
public static IObservable<TSource> Create<TSource>(Func<IObserver<TSource>, IDisposable> subscribe)
|
||||
{
|
||||
return new CreateWithDisposableObservable<TSource>(subscribe);
|
||||
}
|
||||
|
||||
private sealed class CreateWithDisposableObservable<TSource>(Func<IObserver<TSource>, IDisposable> subscribe)
|
||||
: IObservable<TSource>
|
||||
{
|
||||
public IDisposable Subscribe(IObserver<TSource> observer)
|
||||
{
|
||||
return subscribe(observer);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Ursa/Common/XYFocusHelpers.cs
Normal file
23
src/Ursa/Common/XYFocusHelpers.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace Ursa.Common;
|
||||
|
||||
internal static class XYFocusHelpers
|
||||
{
|
||||
internal static bool IsAllowedXYNavigationMode(this InputElement visual, KeyDeviceType? keyDeviceType)
|
||||
{
|
||||
return IsAllowedXYNavigationMode(XYFocus.GetNavigationModes(visual), keyDeviceType);
|
||||
}
|
||||
|
||||
private static bool IsAllowedXYNavigationMode(XYFocusNavigationModes modes, KeyDeviceType? keyDeviceType)
|
||||
{
|
||||
return keyDeviceType switch
|
||||
{
|
||||
null => !modes.Equals(XYFocusNavigationModes.Disabled), // programmatic input, allow any subtree except Disabled.
|
||||
KeyDeviceType.Keyboard => modes.HasFlag(XYFocusNavigationModes.Keyboard),
|
||||
KeyDeviceType.Gamepad => modes.HasFlag(XYFocusNavigationModes.Gamepad),
|
||||
KeyDeviceType.Remote => modes.HasFlag(XYFocusNavigationModes.Remote),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(keyDeviceType), keyDeviceType, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Headless.XUnit;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using HeadlessTest.Ursa.TestHelpers;
|
||||
using Ursa.Controls;
|
||||
using AutoCompleteBox = Avalonia.Controls.AutoCompleteBox;
|
||||
|
||||
namespace HeadlessTest.Ursa.Controls.AutoCompleteBoxTests;
|
||||
|
||||
public class MultiAutoCompleteBoxTests
|
||||
{
|
||||
[AvaloniaFact]
|
||||
public void Search_Filters()
|
||||
{
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.Contains)("am", "name"));
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.Contains)("AME", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.Contains)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.ContainsCaseSensitive)("na", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.ContainsCaseSensitive)("AME", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.ContainsCaseSensitive)("hello", "name"));
|
||||
|
||||
Assert.Null(GetFilter(AutoCompleteFilterMode.Custom));
|
||||
Assert.Null(GetFilter(AutoCompleteFilterMode.None));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.Equals)("na", "na"));
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.Equals)("na", "NA"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.Equals)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive)("na", "na"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive)("na", "NA"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.StartsWith)("na", "name"));
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.StartsWith)("NAM", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.StartsWith)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.StartsWithCaseSensitive)("na", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithCaseSensitive)("NAM", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithCaseSensitive)("hello", "name"));
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void Ordinal_Search_Filters()
|
||||
{
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.ContainsOrdinal)("am", "name"));
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.ContainsOrdinal)("AME", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.ContainsOrdinal)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.ContainsOrdinalCaseSensitive)("na", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.ContainsOrdinalCaseSensitive)("AME", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.ContainsOrdinalCaseSensitive)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.EqualsOrdinal)("na", "na"));
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.EqualsOrdinal)("na", "NA"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.EqualsOrdinal)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.EqualsOrdinalCaseSensitive)("na", "na"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.EqualsOrdinalCaseSensitive)("na", "NA"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.EqualsOrdinalCaseSensitive)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.StartsWithOrdinal)("na", "name"));
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.StartsWithOrdinal)("NAM", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithOrdinal)("hello", "name"));
|
||||
|
||||
Assert.True(GetFilter(AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive)("na", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive)("NAM", "name"));
|
||||
Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive)("hello", "name"));
|
||||
}
|
||||
|
||||
private static AutoCompleteFilterPredicate<string> GetFilter(AutoCompleteFilterMode mode)
|
||||
{
|
||||
return new MultiAutoCompleteBox { FilterMode = mode }
|
||||
.TextFilter;
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void Fires_DropDown_Event()
|
||||
{
|
||||
Window window = new Window();
|
||||
var control = new MultiAutoCompleteBox(){ MinimumPrefixLength = 1 };
|
||||
|
||||
bool openEvent = false;
|
||||
bool closeEvent = false;
|
||||
control.DropDownOpened += (s, e) => openEvent = true;
|
||||
control.DropDownClosed += (s, e) => closeEvent = true;
|
||||
control.ItemsSource = CreateSimpleStringArray();
|
||||
|
||||
window.Content = control;
|
||||
window.Show();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
|
||||
var textBox = control.FindDescendantOfType<TextBox>();
|
||||
|
||||
Assert.NotNull(textBox);
|
||||
|
||||
textBox.Text = "a";
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
Assert.True(control.SearchText == "a");
|
||||
Assert.True(control.IsDropDownOpen);
|
||||
Assert.True(openEvent);
|
||||
|
||||
textBox.Text = String.Empty;
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
Assert.True(control.SearchText == String.Empty);
|
||||
Assert.False(control.IsDropDownOpen);
|
||||
Assert.True(closeEvent);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void Custom_FilterMode_Without_ItemFilter_Setting_Throws_Exception()
|
||||
{
|
||||
Window window = new Window();
|
||||
var control = new MultiAutoCompleteBox()
|
||||
{
|
||||
ItemsSource = CreateSimpleStringArray()
|
||||
};
|
||||
window.Content = control;
|
||||
window.Show();
|
||||
|
||||
control.FilterMode = AutoCompleteFilterMode.Custom;
|
||||
Assert.Throws<Exception>(() => { control.Text = "a"; });
|
||||
}
|
||||
|
||||
private static IList<string> CreateSimpleStringArray()
|
||||
{
|
||||
return new List<string>
|
||||
{
|
||||
"a",
|
||||
"abide",
|
||||
"able",
|
||||
"about",
|
||||
"above",
|
||||
"absence",
|
||||
"absurd",
|
||||
"accept",
|
||||
"acceptance",
|
||||
"accepted",
|
||||
"accepting",
|
||||
"access",
|
||||
"accessed",
|
||||
"accessible",
|
||||
"accident",
|
||||
"accidentally",
|
||||
"accordance",
|
||||
"account",
|
||||
"accounting",
|
||||
"accounts",
|
||||
"accusation",
|
||||
"accustomed",
|
||||
"ache",
|
||||
"across",
|
||||
"act",
|
||||
"active",
|
||||
"actual",
|
||||
"actually",
|
||||
"ada",
|
||||
"added",
|
||||
"adding",
|
||||
"addition",
|
||||
"additional",
|
||||
"additions",
|
||||
"address",
|
||||
"addressed",
|
||||
"addresses",
|
||||
"addressing",
|
||||
"adjourn",
|
||||
"adoption",
|
||||
"advance",
|
||||
"advantage",
|
||||
"adventures",
|
||||
"advice",
|
||||
"advisable",
|
||||
"advise",
|
||||
"affair",
|
||||
"affectionately",
|
||||
"afford",
|
||||
"afore",
|
||||
"afraid",
|
||||
"after",
|
||||
"afterwards",
|
||||
"again",
|
||||
"against",
|
||||
"age",
|
||||
"aged",
|
||||
"agent",
|
||||
"ago",
|
||||
"agony",
|
||||
"agree",
|
||||
"agreed",
|
||||
"agreement",
|
||||
"ah",
|
||||
"ahem",
|
||||
"air",
|
||||
"airs",
|
||||
"ak",
|
||||
"alarm",
|
||||
"alarmed",
|
||||
"alas",
|
||||
"alice",
|
||||
"alive",
|
||||
"all",
|
||||
"allow",
|
||||
"almost",
|
||||
"alone",
|
||||
"along",
|
||||
"aloud",
|
||||
"already",
|
||||
"also",
|
||||
"alteration",
|
||||
"altered",
|
||||
"alternate",
|
||||
"alternately",
|
||||
"altogether",
|
||||
"always",
|
||||
"am",
|
||||
"ambition",
|
||||
"among",
|
||||
"an",
|
||||
"ancient",
|
||||
"and",
|
||||
"anger",
|
||||
"angrily",
|
||||
"angry",
|
||||
"animal",
|
||||
"animals",
|
||||
"ann",
|
||||
"annoy",
|
||||
"annoyed",
|
||||
"another",
|
||||
"answer",
|
||||
"answered",
|
||||
"answers",
|
||||
"antipathies",
|
||||
"anxious",
|
||||
"anxiously",
|
||||
"any",
|
||||
"anyone",
|
||||
"anything",
|
||||
"anywhere",
|
||||
"appealed",
|
||||
"appear",
|
||||
"appearance",
|
||||
"appeared",
|
||||
"appearing",
|
||||
"appears",
|
||||
"applause",
|
||||
"apple",
|
||||
"apples",
|
||||
"applicable",
|
||||
"apply",
|
||||
"approach",
|
||||
"arch",
|
||||
"archbishop",
|
||||
"arches",
|
||||
"archive",
|
||||
"are",
|
||||
"argue",
|
||||
"argued",
|
||||
"argument",
|
||||
"arguments",
|
||||
"arise",
|
||||
"arithmetic",
|
||||
"arm",
|
||||
"arms",
|
||||
"around",
|
||||
"arranged",
|
||||
"array",
|
||||
"arrived",
|
||||
"arrow",
|
||||
"arrum",
|
||||
"as",
|
||||
"ascii",
|
||||
"ashamed",
|
||||
"ask",
|
||||
"askance",
|
||||
"asked",
|
||||
"asking",
|
||||
"asleep",
|
||||
"assembled",
|
||||
"assistance",
|
||||
"associated",
|
||||
"at",
|
||||
"ate",
|
||||
"atheling",
|
||||
"atom",
|
||||
"attached",
|
||||
"attempt",
|
||||
"attempted",
|
||||
"attempts",
|
||||
"attended",
|
||||
"attending",
|
||||
"attends",
|
||||
"audibly",
|
||||
"australia",
|
||||
"author",
|
||||
"authority",
|
||||
"available",
|
||||
"avoid",
|
||||
"away",
|
||||
"awfully",
|
||||
"axes",
|
||||
"axis",
|
||||
"b",
|
||||
"baby",
|
||||
"back",
|
||||
"backs",
|
||||
"bad",
|
||||
"bag",
|
||||
"baked",
|
||||
"balanced",
|
||||
"bank",
|
||||
"banks",
|
||||
"banquet",
|
||||
"bark",
|
||||
"barking",
|
||||
"barley",
|
||||
"barrowful",
|
||||
"based",
|
||||
"bat",
|
||||
"bathing",
|
||||
"bats",
|
||||
"bawled",
|
||||
"be",
|
||||
"beak",
|
||||
"bear",
|
||||
"beast",
|
||||
"beasts",
|
||||
"beat",
|
||||
"beating",
|
||||
"beau",
|
||||
"beauti",
|
||||
"beautiful",
|
||||
"beautifully",
|
||||
"beautify",
|
||||
"became",
|
||||
"because",
|
||||
"become",
|
||||
"becoming",
|
||||
"bed",
|
||||
"beds",
|
||||
"bee",
|
||||
"been",
|
||||
"before",
|
||||
"beg",
|
||||
"began",
|
||||
"begged",
|
||||
"begin",
|
||||
"beginning",
|
||||
"begins",
|
||||
"begun",
|
||||
"behead",
|
||||
"beheaded",
|
||||
"beheading",
|
||||
"behind",
|
||||
"being",
|
||||
"believe",
|
||||
"believed",
|
||||
"bells",
|
||||
"belong",
|
||||
"belongs",
|
||||
"beloved",
|
||||
"below",
|
||||
"belt",
|
||||
"bend",
|
||||
"bent",
|
||||
"besides",
|
||||
"best",
|
||||
"better",
|
||||
"between",
|
||||
"bill",
|
||||
"binary",
|
||||
"bird",
|
||||
"birds",
|
||||
"birthday",
|
||||
"bit",
|
||||
"bite",
|
||||
"bitter",
|
||||
"blacking",
|
||||
"blades",
|
||||
"blame",
|
||||
"blasts",
|
||||
"bleeds",
|
||||
"blew",
|
||||
"blow",
|
||||
"blown",
|
||||
"blows",
|
||||
"body",
|
||||
"boldly",
|
||||
"bone",
|
||||
"bones",
|
||||
"book",
|
||||
"books",
|
||||
"boon",
|
||||
"boots",
|
||||
"bore",
|
||||
"both",
|
||||
"bother",
|
||||
"bottle",
|
||||
"bottom",
|
||||
"bough",
|
||||
"bound",
|
||||
"bowed",
|
||||
"bowing",
|
||||
"box",
|
||||
"boxed",
|
||||
"boy",
|
||||
"brain",
|
||||
"branch",
|
||||
"branches",
|
||||
"brandy",
|
||||
"brass",
|
||||
"brave",
|
||||
"breach",
|
||||
"bread",
|
||||
"break",
|
||||
"breath",
|
||||
"breathe",
|
||||
"breeze",
|
||||
"bright",
|
||||
"brightened",
|
||||
"bring",
|
||||
"bringing",
|
||||
"bristling",
|
||||
"broke",
|
||||
"broken",
|
||||
"brother",
|
||||
"brought",
|
||||
"brown",
|
||||
"brush",
|
||||
"brushing",
|
||||
"burn",
|
||||
"burning",
|
||||
"burnt",
|
||||
"burst",
|
||||
"bursting",
|
||||
"busily",
|
||||
"business",
|
||||
"business@pglaf",
|
||||
"busy",
|
||||
"but",
|
||||
"butter",
|
||||
"buttercup",
|
||||
"buttered",
|
||||
"butterfly",
|
||||
"buttons",
|
||||
"by",
|
||||
"bye",
|
||||
"c",
|
||||
"cackled",
|
||||
"cake",
|
||||
"cakes",
|
||||
"calculate",
|
||||
"calculated",
|
||||
"call",
|
||||
"called",
|
||||
"calling",
|
||||
"calmly",
|
||||
"came",
|
||||
"camomile",
|
||||
"can",
|
||||
"canary",
|
||||
"candle",
|
||||
"cannot",
|
||||
"canterbury",
|
||||
"canvas",
|
||||
"capering",
|
||||
"capital",
|
||||
"card",
|
||||
"cardboard",
|
||||
"cards",
|
||||
"care",
|
||||
"carefully",
|
||||
"cares",
|
||||
"carried",
|
||||
"carrier",
|
||||
"carroll",
|
||||
"carry",
|
||||
"carrying",
|
||||
"cart",
|
||||
"cartwheels",
|
||||
"case",
|
||||
"cat",
|
||||
"catch",
|
||||
"catching",
|
||||
"caterpillar",
|
||||
"cats",
|
||||
"cattle",
|
||||
"caucus",
|
||||
"caught",
|
||||
"cauldron",
|
||||
"cause",
|
||||
"caused",
|
||||
"cautiously",
|
||||
"cease",
|
||||
"ceiling",
|
||||
"centre",
|
||||
"certain",
|
||||
"certainly",
|
||||
"chain",
|
||||
"chains",
|
||||
"chair",
|
||||
"chance",
|
||||
"chanced",
|
||||
"change",
|
||||
"changed",
|
||||
"changes",
|
||||
"changing",
|
||||
"chapter",
|
||||
"character",
|
||||
"charge",
|
||||
"charges",
|
||||
"charitable",
|
||||
"charities",
|
||||
"chatte",
|
||||
"cheap",
|
||||
"cheated",
|
||||
"check",
|
||||
"checked",
|
||||
"checks",
|
||||
"cheeks",
|
||||
"cheered",
|
||||
"cheerfully",
|
||||
"cherry",
|
||||
"cheshire",
|
||||
"chief",
|
||||
"child",
|
||||
"childhood",
|
||||
"children",
|
||||
"chimney",
|
||||
"chimneys",
|
||||
"chin",
|
||||
"choice",
|
||||
"choke",
|
||||
"choked",
|
||||
"choking",
|
||||
"choose",
|
||||
"choosing",
|
||||
"chop",
|
||||
"chorus",
|
||||
"chose",
|
||||
"christmas",
|
||||
"chrysalis",
|
||||
"chuckled",
|
||||
"circle",
|
||||
"circumstances",
|
||||
"city",
|
||||
"civil",
|
||||
"claim",
|
||||
"clamour",
|
||||
"clapping",
|
||||
"clasped",
|
||||
"classics",
|
||||
"claws",
|
||||
"clean",
|
||||
"clear",
|
||||
"cleared",
|
||||
"clearer",
|
||||
"clearly",
|
||||
"clever",
|
||||
"climb",
|
||||
"clinging",
|
||||
"clock",
|
||||
"close",
|
||||
"closed",
|
||||
"closely",
|
||||
"closer",
|
||||
"clubs",
|
||||
"coast",
|
||||
"coaxing",
|
||||
"codes",
|
||||
"coils",
|
||||
"cold",
|
||||
"collar",
|
||||
"collected",
|
||||
"collection",
|
||||
"come",
|
||||
"comes",
|
||||
"comfits",
|
||||
"comfort",
|
||||
"comfortable",
|
||||
"comfortably",
|
||||
"coming",
|
||||
"commercial",
|
||||
"committed",
|
||||
"common",
|
||||
"commotion",
|
||||
"company",
|
||||
"compilation",
|
||||
"complained",
|
||||
"complaining",
|
||||
"completely",
|
||||
"compliance",
|
||||
"comply",
|
||||
"complying",
|
||||
"compressed",
|
||||
"computer",
|
||||
"computers",
|
||||
"concept",
|
||||
"concerning",
|
||||
"concert",
|
||||
"concluded",
|
||||
"conclusion",
|
||||
"condemn",
|
||||
"conduct",
|
||||
"confirmation",
|
||||
"confirmed",
|
||||
"confused",
|
||||
"confusing",
|
||||
"confusion",
|
||||
"conger",
|
||||
"conqueror",
|
||||
"conquest",
|
||||
"consented",
|
||||
"consequential",
|
||||
"consider",
|
||||
"considerable",
|
||||
"considered",
|
||||
"considering",
|
||||
"constant",
|
||||
"consultation",
|
||||
"contact",
|
||||
"contain",
|
||||
"containing",
|
||||
"contempt",
|
||||
"contemptuous",
|
||||
"contemptuously",
|
||||
"content",
|
||||
"continued",
|
||||
"contract",
|
||||
"contradicted",
|
||||
"contributions",
|
||||
"conversation",
|
||||
"conversations",
|
||||
"convert",
|
||||
"cook",
|
||||
"cool",
|
||||
"copied",
|
||||
"copies",
|
||||
"copy",
|
||||
"copying",
|
||||
"copyright",
|
||||
"corner",
|
||||
"corners",
|
||||
"corporation",
|
||||
"corrupt",
|
||||
"cost",
|
||||
"costs",
|
||||
"could",
|
||||
"couldn",
|
||||
"counting",
|
||||
"countries",
|
||||
"country",
|
||||
"couple",
|
||||
"couples",
|
||||
"courage",
|
||||
"course",
|
||||
"court",
|
||||
"courtiers",
|
||||
"coward",
|
||||
"crab",
|
||||
"crash",
|
||||
"crashed",
|
||||
"crawled",
|
||||
"crawling",
|
||||
"crazy",
|
||||
"created",
|
||||
"creating",
|
||||
"creation",
|
||||
"creature",
|
||||
"creatures",
|
||||
"credit",
|
||||
"creep",
|
||||
"crept",
|
||||
"cried",
|
||||
"cries",
|
||||
"crimson",
|
||||
"critical",
|
||||
"crocodile",
|
||||
"croquet",
|
||||
"croqueted",
|
||||
"croqueting",
|
||||
"cross",
|
||||
"crossed",
|
||||
"crossly",
|
||||
"crouched",
|
||||
"crowd",
|
||||
"crowded",
|
||||
"crown",
|
||||
"crumbs",
|
||||
"crust",
|
||||
"cry",
|
||||
"crying",
|
||||
"cucumber",
|
||||
"cunning",
|
||||
"cup",
|
||||
"cupboards",
|
||||
"cur",
|
||||
"curiosity",
|
||||
"curious",
|
||||
"curiouser",
|
||||
"curled",
|
||||
"curls",
|
||||
"curly",
|
||||
"currants",
|
||||
"current",
|
||||
"curtain",
|
||||
"curtsey",
|
||||
"curtseying",
|
||||
"curving",
|
||||
"cushion",
|
||||
"custard",
|
||||
"custody",
|
||||
"cut",
|
||||
"cutting",
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user