Sicherung
This commit is contained in:
32
AutoCompleteTextBox/AutoCompleteTextBox.csproj
Normal file
32
AutoCompleteTextBox/AutoCompleteTextBox.csproj
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<PropertyGroup>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<TargetFrameworks>net48;netcoreapp3.1;net6.0-windows</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Version>1.6.0.0</Version>
|
||||
<PackageProjectUrl>https://github.com/quicoli/WPF-AutoComplete-TextBox</PackageProjectUrl>
|
||||
<RepositoryType />
|
||||
<RepositoryUrl>https://github.com/quicoli/WPF-AutoComplete-TextBox</RepositoryUrl>
|
||||
<PackageTags>wpf, autocomplete, usercontrol</PackageTags>
|
||||
<PackageIconUrl>https://github.com/quicoli/WPF-AutoComplete-TextBox/blob/develop/AutoCompleteTextBox/Logo/AutoCompleteTextBox.ico?raw=true</PackageIconUrl>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<ApplicationIcon>AutoCompleteTextBox.ico</ApplicationIcon>
|
||||
<PackageReleaseNotes>
|
||||
Better support for keyboard focus
|
||||
</PackageReleaseNotes>
|
||||
<Description>An auto complete textbox and combo box for WPF</Description>
|
||||
<PackageIcon>AutoCompleteTextBox.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\README.md">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="..\Logo\AutoCompleteTextBox.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
BIN
AutoCompleteTextBox/AutoCompleteTextBox.ico
Normal file
BIN
AutoCompleteTextBox/AutoCompleteTextBox.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 492 B |
49
AutoCompleteTextBox/BindingEvaluator.cs
Normal file
49
AutoCompleteTextBox/BindingEvaluator.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace AutoCompleteTextBox
|
||||
{
|
||||
public class BindingEvaluator : FrameworkElement
|
||||
{
|
||||
|
||||
#region "Fields"
|
||||
|
||||
|
||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(BindingEvaluator), new FrameworkPropertyMetadata(string.Empty));
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Constructors"
|
||||
|
||||
public BindingEvaluator(Binding binding)
|
||||
{
|
||||
ValueBinding = binding;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties"
|
||||
|
||||
public string Value
|
||||
{
|
||||
get => (string)GetValue(ValueProperty);
|
||||
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public Binding ValueBinding { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods"
|
||||
|
||||
public string Evaluate(object dataItem)
|
||||
{
|
||||
DataContext = dataItem;
|
||||
SetBinding(ValueProperty, ValueBinding);
|
||||
return Value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
612
AutoCompleteTextBox/Editors/AutoCompleteComboBox.cs
Normal file
612
AutoCompleteTextBox/Editors/AutoCompleteComboBox.cs
Normal file
@@ -0,0 +1,612 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace AutoCompleteTextBox.Editors
|
||||
{
|
||||
[TemplatePart(Name = PartEditor, Type = typeof(TextBox))]
|
||||
[TemplatePart(Name = PartPopup, Type = typeof(Popup))]
|
||||
[TemplatePart(Name = PartSelector, Type = typeof(Selector))]
|
||||
[TemplatePart(Name = PartExpander, Type = typeof(Expander))]
|
||||
public class AutoCompleteComboBox : Control
|
||||
{
|
||||
|
||||
#region "Fields"
|
||||
|
||||
public const string PartEditor = "PART_Editor";
|
||||
public const string PartPopup = "PART_Popup";
|
||||
|
||||
public const string PartSelector = "PART_Selector";
|
||||
public const string PartExpander = "PART_Expander";
|
||||
public static readonly DependencyProperty DelayProperty = DependencyProperty.Register("Delay", typeof(int), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(200));
|
||||
public static readonly DependencyProperty DisplayMemberProperty = DependencyProperty.Register("DisplayMember", typeof(string), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty IconPlacementProperty = DependencyProperty.Register("IconPlacement", typeof(IconPlacement), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(IconPlacement.Left));
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(object), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null));
|
||||
public static readonly DependencyProperty IconVisibilityProperty = DependencyProperty.Register("IconVisibility", typeof(Visibility), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(Visibility.Visible));
|
||||
public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register("IsLoading", typeof(bool), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(false));
|
||||
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null));
|
||||
public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(AutoCompleteComboBox));
|
||||
public static readonly DependencyProperty LoadingContentProperty = DependencyProperty.Register("LoadingContent", typeof(object), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null));
|
||||
public static readonly DependencyProperty ProviderProperty = DependencyProperty.Register("Provider", typeof(IComboSuggestionProvider), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null));
|
||||
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null, OnSelectedItemChanged));
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(string.Empty, propertyChangedCallback: null, coerceValueCallback: null, isAnimationProhibited: false, defaultUpdateSourceTrigger: UpdateSourceTrigger.LostFocus, flags: FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||
public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter", typeof(string), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(0));
|
||||
public static readonly DependencyProperty CharacterCasingProperty = DependencyProperty.Register("CharacterCasing", typeof(CharacterCasing), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(CharacterCasing.Normal));
|
||||
public static readonly DependencyProperty MaxPopUpHeightProperty = DependencyProperty.Register("MaxPopUpHeight", typeof(int), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(600));
|
||||
public static readonly DependencyProperty MaxPopUpWidthProperty = DependencyProperty.Register("MaxPopUpWidth", typeof(int), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(2000));
|
||||
|
||||
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register("Watermark", typeof(string), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(string.Empty));
|
||||
|
||||
public static readonly DependencyProperty SuggestionBackgroundProperty = DependencyProperty.Register("SuggestionBackground", typeof(Brush), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(Brushes.White));
|
||||
private bool _isUpdatingText;
|
||||
private bool _selectionCancelled;
|
||||
|
||||
private SuggestionsAdapter _suggestionsAdapter;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Constructors"
|
||||
|
||||
static AutoCompleteComboBox()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(typeof(AutoCompleteComboBox)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties"
|
||||
|
||||
|
||||
public int MaxPopupHeight
|
||||
{
|
||||
get => (int)GetValue(MaxPopUpHeightProperty);
|
||||
set => SetValue(MaxPopUpHeightProperty, value);
|
||||
}
|
||||
public int MaxPopupWidth
|
||||
{
|
||||
get => (int)GetValue(MaxPopUpWidthProperty);
|
||||
set => SetValue(MaxPopUpWidthProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public BindingEvaluator BindingEvaluator { get; set; }
|
||||
|
||||
public CharacterCasing CharacterCasing
|
||||
{
|
||||
get => (CharacterCasing)GetValue(CharacterCasingProperty);
|
||||
set => SetValue(CharacterCasingProperty, value);
|
||||
}
|
||||
|
||||
public int MaxLength
|
||||
{
|
||||
get => (int)GetValue(MaxLengthProperty);
|
||||
set => SetValue(MaxLengthProperty, value);
|
||||
}
|
||||
|
||||
public int Delay
|
||||
{
|
||||
get => (int)GetValue(DelayProperty);
|
||||
|
||||
set => SetValue(DelayProperty, value);
|
||||
}
|
||||
|
||||
public string DisplayMember
|
||||
{
|
||||
get => (string)GetValue(DisplayMemberProperty);
|
||||
|
||||
set => SetValue(DisplayMemberProperty, value);
|
||||
}
|
||||
|
||||
public TextBox Editor { get; set; }
|
||||
public Expander Expander { get; set; }
|
||||
|
||||
public DispatcherTimer FetchTimer { get; set; }
|
||||
|
||||
public string Filter
|
||||
{
|
||||
get => (string)GetValue(FilterProperty);
|
||||
|
||||
set => SetValue(FilterProperty, value);
|
||||
}
|
||||
|
||||
public object Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public IconPlacement IconPlacement
|
||||
{
|
||||
get => (IconPlacement)GetValue(IconPlacementProperty);
|
||||
|
||||
set => SetValue(IconPlacementProperty, value);
|
||||
}
|
||||
|
||||
public Visibility IconVisibility
|
||||
{
|
||||
get => (Visibility)GetValue(IconVisibilityProperty);
|
||||
|
||||
set => SetValue(IconVisibilityProperty, value);
|
||||
}
|
||||
|
||||
public bool IsDropDownOpen
|
||||
{
|
||||
get => (bool)GetValue(IsDropDownOpenProperty);
|
||||
|
||||
set
|
||||
{
|
||||
this.Expander.IsExpanded = value;
|
||||
SetValue(IsDropDownOpenProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLoading
|
||||
{
|
||||
get => (bool)GetValue(IsLoadingProperty);
|
||||
|
||||
set => SetValue(IsLoadingProperty, value);
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get => (bool)GetValue(IsReadOnlyProperty);
|
||||
|
||||
set => SetValue(IsReadOnlyProperty, value);
|
||||
}
|
||||
|
||||
public Selector ItemsSelector { get; set; }
|
||||
|
||||
public DataTemplate ItemTemplate
|
||||
{
|
||||
get => (DataTemplate)GetValue(ItemTemplateProperty);
|
||||
|
||||
set => SetValue(ItemTemplateProperty, value);
|
||||
}
|
||||
|
||||
public DataTemplateSelector ItemTemplateSelector
|
||||
{
|
||||
get => ((DataTemplateSelector)(GetValue(ItemTemplateSelectorProperty)));
|
||||
set => SetValue(ItemTemplateSelectorProperty, value);
|
||||
}
|
||||
|
||||
public object LoadingContent
|
||||
{
|
||||
get => GetValue(LoadingContentProperty);
|
||||
|
||||
set => SetValue(LoadingContentProperty, value);
|
||||
}
|
||||
|
||||
public Popup Popup { get; set; }
|
||||
|
||||
public IComboSuggestionProvider Provider
|
||||
{
|
||||
get => (IComboSuggestionProvider)GetValue(ProviderProperty);
|
||||
|
||||
set => SetValue(ProviderProperty, value);
|
||||
}
|
||||
|
||||
public object SelectedItem
|
||||
{
|
||||
get => GetValue(SelectedItemProperty);
|
||||
|
||||
set => SetValue(SelectedItemProperty, value);
|
||||
}
|
||||
|
||||
public SelectionAdapter SelectionAdapter { get; set; }
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public string Watermark
|
||||
{
|
||||
get => (string)GetValue(WatermarkProperty);
|
||||
|
||||
set => SetValue(WatermarkProperty, value);
|
||||
}
|
||||
public Brush SuggestionBackground
|
||||
{
|
||||
get => (Brush)GetValue(SuggestionBackgroundProperty);
|
||||
|
||||
set => SetValue(SuggestionBackgroundProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods"
|
||||
|
||||
public static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
AutoCompleteComboBox act = null;
|
||||
act = d as AutoCompleteComboBox;
|
||||
if (act != null)
|
||||
{
|
||||
if (act.Editor != null & !act._isUpdatingText)
|
||||
{
|
||||
act._isUpdatingText = true;
|
||||
act.Editor.Text = act.BindingEvaluator.Evaluate(e.NewValue);
|
||||
act._isUpdatingText = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToSelectedItem()
|
||||
{
|
||||
if (ItemsSelector is ListBox listBox && listBox.SelectedItem != null)
|
||||
listBox.ScrollIntoView(listBox.SelectedItem);
|
||||
}
|
||||
|
||||
public new BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding){
|
||||
var res = base.SetBinding(dp, binding);
|
||||
CheckForParentTextBindingChange();
|
||||
return res;
|
||||
}
|
||||
public new BindingExpressionBase SetBinding(DependencyProperty dp, String path) {
|
||||
var res = base.SetBinding(dp, path);
|
||||
CheckForParentTextBindingChange();
|
||||
return res;
|
||||
}
|
||||
public new void ClearValue(DependencyPropertyKey key) {
|
||||
base.ClearValue(key);
|
||||
CheckForParentTextBindingChange();
|
||||
}
|
||||
public new void ClearValue(DependencyProperty dp) {
|
||||
base.ClearValue(dp);
|
||||
CheckForParentTextBindingChange();
|
||||
}
|
||||
private void CheckForParentTextBindingChange(bool force=false) {
|
||||
var CurrentBindingMode = BindingOperations.GetBinding(this, TextProperty)?.UpdateSourceTrigger ?? UpdateSourceTrigger.Default;
|
||||
if (CurrentBindingMode != UpdateSourceTrigger.PropertyChanged)//preventing going any less frequent than property changed
|
||||
CurrentBindingMode = UpdateSourceTrigger.Default;
|
||||
|
||||
|
||||
if (CurrentBindingMode == CurrentTextboxTextBindingUpdateMode && force == false)
|
||||
return;
|
||||
var binding = new Binding {
|
||||
Mode = BindingMode.TwoWay,
|
||||
UpdateSourceTrigger = CurrentBindingMode,
|
||||
Path = new PropertyPath(nameof(Text)),
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
|
||||
};
|
||||
CurrentTextboxTextBindingUpdateMode = CurrentBindingMode;
|
||||
Editor?.SetBinding(TextBox.TextProperty, binding);
|
||||
}
|
||||
|
||||
private UpdateSourceTrigger CurrentTextboxTextBindingUpdateMode;
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
Editor = Template.FindName(PartEditor, this) as TextBox;
|
||||
Popup = Template.FindName(PartPopup, this) as Popup;
|
||||
ItemsSelector = Template.FindName(PartSelector, this) as Selector;
|
||||
Expander = Template.FindName(PartExpander, this) as Expander;
|
||||
|
||||
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
|
||||
|
||||
if (Editor != null)
|
||||
{
|
||||
Editor.TextChanged += OnEditorTextChanged;
|
||||
Editor.PreviewKeyDown += OnEditorKeyDown;
|
||||
Editor.LostFocus += OnEditorLostFocus;
|
||||
CheckForParentTextBindingChange(true);
|
||||
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
_isUpdatingText = true;
|
||||
Editor.Text = BindingEvaluator.Evaluate(SelectedItem);
|
||||
_isUpdatingText = false;
|
||||
}
|
||||
|
||||
}
|
||||
if (Expander != null)
|
||||
{
|
||||
Expander.IsExpanded = false;
|
||||
Expander.Collapsed += Expander_Expanded;
|
||||
Expander.Expanded += Expander_Expanded;
|
||||
}
|
||||
|
||||
GotFocus += AutoCompleteComboBox_GotFocus;
|
||||
|
||||
if (Popup != null)
|
||||
{
|
||||
Popup.StaysOpen = false;
|
||||
Popup.Opened += OnPopupOpened;
|
||||
Popup.Closed += OnPopupClosed;
|
||||
}
|
||||
if (ItemsSelector != null)
|
||||
{
|
||||
SelectionAdapter = new SelectionAdapter(ItemsSelector);
|
||||
SelectionAdapter.Commit += OnSelectionAdapterCommit;
|
||||
SelectionAdapter.Cancel += OnSelectionAdapterCancel;
|
||||
SelectionAdapter.SelectionChanged += OnSelectionAdapterSelectionChanged;
|
||||
ItemsSelector.PreviewMouseDown += ItemsSelector_PreviewMouseDown;
|
||||
}
|
||||
}
|
||||
|
||||
private void Expander_Expanded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.IsDropDownOpen = Expander.IsExpanded;
|
||||
if (!this.IsDropDownOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_suggestionsAdapter == null)
|
||||
{
|
||||
_suggestionsAdapter = new SuggestionsAdapter(this);
|
||||
}
|
||||
if (SelectedItem != null || String.IsNullOrWhiteSpace(Editor.Text))
|
||||
_suggestionsAdapter.ShowFullCollection();
|
||||
|
||||
}
|
||||
|
||||
private void ItemsSelector_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if ((e.OriginalSource as FrameworkElement)?.DataContext == null)
|
||||
return;
|
||||
if (!ItemsSelector.Items.Contains(((FrameworkElement)e.OriginalSource)?.DataContext))
|
||||
return;
|
||||
ItemsSelector.SelectedItem = ((FrameworkElement)e.OriginalSource)?.DataContext;
|
||||
OnSelectionAdapterCommit(SelectionAdapter.EventCause.ItemClicked);
|
||||
e.Handled = true;
|
||||
}
|
||||
private void AutoCompleteComboBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Editor?.Focus();
|
||||
}
|
||||
|
||||
private string GetDisplayText(object dataItem)
|
||||
{
|
||||
if (BindingEvaluator == null)
|
||||
{
|
||||
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
|
||||
}
|
||||
if (dataItem == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (string.IsNullOrEmpty(DisplayMember))
|
||||
{
|
||||
return dataItem.ToString();
|
||||
}
|
||||
return BindingEvaluator.Evaluate(dataItem);
|
||||
}
|
||||
|
||||
private void OnEditorKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (SelectionAdapter != null)
|
||||
{
|
||||
if (IsDropDownOpen)
|
||||
SelectionAdapter.HandleKeyDown(e);
|
||||
else
|
||||
IsDropDownOpen = e.Key == Key.Down || e.Key == Key.Up;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEditorLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!IsKeyboardFocusWithin)
|
||||
{
|
||||
IsDropDownOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEditorTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
Text = Editor.Text;
|
||||
if (_isUpdatingText)
|
||||
return;
|
||||
if (FetchTimer == null)
|
||||
{
|
||||
FetchTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(Delay) };
|
||||
FetchTimer.Tick += OnFetchTimerTick;
|
||||
}
|
||||
FetchTimer.IsEnabled = false;
|
||||
FetchTimer.Stop();
|
||||
SetSelectedItem(null);
|
||||
if (Editor.Text.Length > 0)
|
||||
{
|
||||
FetchTimer.IsEnabled = true;
|
||||
FetchTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
IsDropDownOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFetchTimerTick(object sender, EventArgs e)
|
||||
{
|
||||
FetchTimer.IsEnabled = false;
|
||||
FetchTimer.Stop();
|
||||
if (Provider != null && ItemsSelector != null)
|
||||
{
|
||||
Filter = Editor.Text;
|
||||
if (_suggestionsAdapter == null)
|
||||
{
|
||||
_suggestionsAdapter = new SuggestionsAdapter(this);
|
||||
}
|
||||
_suggestionsAdapter.GetSuggestions(Filter);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPopupClosed(object sender, EventArgs e)
|
||||
{
|
||||
if (!_selectionCancelled)
|
||||
{
|
||||
OnSelectionAdapterCommit(SelectionAdapter.EventCause.PopupClosed);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPopupOpened(object sender, EventArgs e)
|
||||
{
|
||||
_selectionCancelled = false;
|
||||
ItemsSelector.SelectedItem = SelectedItem;
|
||||
}
|
||||
|
||||
public event EventHandler<SelectionAdapter.PreSelectionAdapterFinishArgs> PreSelectionAdapterFinish;
|
||||
private bool PreSelectionEventSomeoneHandled(SelectionAdapter.EventCause cause, bool is_cancel) {
|
||||
if (PreSelectionAdapterFinish == null)
|
||||
return false;
|
||||
var args = new SelectionAdapter.PreSelectionAdapterFinishArgs { cause = cause, is_cancel = is_cancel };
|
||||
PreSelectionAdapterFinish?.Invoke(this, args);
|
||||
return args.handled;
|
||||
|
||||
}
|
||||
private void OnSelectionAdapterCancel(SelectionAdapter.EventCause cause)
|
||||
{
|
||||
if (PreSelectionEventSomeoneHandled(cause, true))
|
||||
return;
|
||||
_isUpdatingText = true;
|
||||
Editor.Text = SelectedItem == null ? Filter : GetDisplayText(SelectedItem);
|
||||
Editor.SelectionStart = Editor.Text.Length;
|
||||
Editor.SelectionLength = 0;
|
||||
_isUpdatingText = false;
|
||||
IsDropDownOpen = false;
|
||||
_selectionCancelled = true;
|
||||
}
|
||||
|
||||
private void OnSelectionAdapterCommit(SelectionAdapter.EventCause cause)
|
||||
{
|
||||
if (PreSelectionEventSomeoneHandled(cause, false))
|
||||
return;
|
||||
|
||||
if (ItemsSelector.SelectedItem != null)
|
||||
{
|
||||
SelectedItem = ItemsSelector.SelectedItem;
|
||||
_isUpdatingText = true;
|
||||
Editor.Text = GetDisplayText(ItemsSelector.SelectedItem);
|
||||
SetSelectedItem(ItemsSelector.SelectedItem);
|
||||
_isUpdatingText = false;
|
||||
IsDropDownOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionAdapterSelectionChanged()
|
||||
{
|
||||
_isUpdatingText = true;
|
||||
Editor.Text = ItemsSelector.SelectedItem == null ? Filter : GetDisplayText(ItemsSelector.SelectedItem);
|
||||
Editor.SelectionStart = Editor.Text.Length;
|
||||
Editor.SelectionLength = 0;
|
||||
ScrollToSelectedItem();
|
||||
_isUpdatingText = false;
|
||||
}
|
||||
|
||||
private void SetSelectedItem(object item)
|
||||
{
|
||||
_isUpdatingText = true;
|
||||
SelectedItem = item;
|
||||
_isUpdatingText = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Nested Types"
|
||||
|
||||
private class SuggestionsAdapter
|
||||
{
|
||||
|
||||
#region "Fields"
|
||||
|
||||
private readonly AutoCompleteComboBox _actb;
|
||||
|
||||
private string _filter;
|
||||
#endregion
|
||||
|
||||
#region "Constructors"
|
||||
|
||||
public SuggestionsAdapter(AutoCompleteComboBox actb)
|
||||
{
|
||||
_actb = actb;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods"
|
||||
|
||||
public void GetSuggestions(string searchText)
|
||||
{
|
||||
_actb.IsLoading = true;
|
||||
// Do not open drop down if control is not focused
|
||||
if (_actb.IsKeyboardFocusWithin)
|
||||
_actb.IsDropDownOpen = true;
|
||||
_actb.ItemsSelector.ItemsSource = null;
|
||||
ParameterizedThreadStart thInfo = GetSuggestionsAsync;
|
||||
Thread th = new Thread(thInfo);
|
||||
_filter = searchText;
|
||||
th.Start(new object[] { searchText, _actb.Provider });
|
||||
}
|
||||
public void ShowFullCollection()
|
||||
{
|
||||
_filter = string.Empty;
|
||||
_actb.IsLoading = true;
|
||||
// Do not open drop down if control is not focused
|
||||
if (_actb.IsKeyboardFocusWithin)
|
||||
_actb.IsDropDownOpen = true;
|
||||
_actb.ItemsSelector.ItemsSource = null;
|
||||
ParameterizedThreadStart thInfo = GetFullCollectionAsync;
|
||||
Thread th = new Thread(thInfo);
|
||||
th.Start(_actb.Provider);
|
||||
}
|
||||
|
||||
private void DisplaySuggestions(IEnumerable suggestions, string filter)
|
||||
{
|
||||
if (_filter != filter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_actb.IsLoading = false;
|
||||
_actb.ItemsSelector.ItemsSource = suggestions;
|
||||
// Close drop down if there are no items
|
||||
if (_actb.IsDropDownOpen)
|
||||
{
|
||||
_actb.IsDropDownOpen = _actb.ItemsSelector.HasItems;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetSuggestionsAsync(object param)
|
||||
{
|
||||
if (param is object[] args)
|
||||
{
|
||||
string searchText = Convert.ToString(args[0]);
|
||||
if (args[1] is IComboSuggestionProvider provider)
|
||||
{
|
||||
IEnumerable list = provider.GetSuggestions(searchText);
|
||||
_actb.Dispatcher.BeginInvoke(new Action<IEnumerable, string>(DisplaySuggestions), DispatcherPriority.Background, list, searchText);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void GetFullCollectionAsync(object param)
|
||||
{
|
||||
if (param is IComboSuggestionProvider provider)
|
||||
{
|
||||
IEnumerable list = provider.GetFullCollection();
|
||||
_actb.Dispatcher.BeginInvoke(new Action<IEnumerable, string>(DisplaySuggestions), DispatcherPriority.Background, list, string.Empty);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
569
AutoCompleteTextBox/Editors/AutoCompleteTextBox.cs
Normal file
569
AutoCompleteTextBox/Editors/AutoCompleteTextBox.cs
Normal file
@@ -0,0 +1,569 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace AutoCompleteTextBox.Editors
|
||||
{
|
||||
[TemplatePart(Name = PartEditor, Type = typeof(TextBox))]
|
||||
[TemplatePart(Name = PartPopup, Type = typeof(Popup))]
|
||||
[TemplatePart(Name = PartSelector, Type = typeof(Selector))]
|
||||
public class AutoCompleteTextBox : Control
|
||||
{
|
||||
|
||||
#region "Fields"
|
||||
|
||||
public const string PartEditor = "PART_Editor";
|
||||
public const string PartPopup = "PART_Popup";
|
||||
|
||||
public const string PartSelector = "PART_Selector";
|
||||
public static readonly DependencyProperty DelayProperty = DependencyProperty.Register("Delay", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(200));
|
||||
public static readonly DependencyProperty DisplayMemberProperty = DependencyProperty.Register("DisplayMember", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty IconPlacementProperty = DependencyProperty.Register("IconPlacement", typeof(IconPlacement), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(IconPlacement.Left));
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
|
||||
public static readonly DependencyProperty IconVisibilityProperty = DependencyProperty.Register("IconVisibility", typeof(Visibility), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(Visibility.Visible));
|
||||
public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register("IsLoading", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
|
||||
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
|
||||
public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(AutoCompleteTextBox));
|
||||
public static readonly DependencyProperty LoadingContentProperty = DependencyProperty.Register("LoadingContent", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
|
||||
public static readonly DependencyProperty ProviderProperty = DependencyProperty.Register("Provider", typeof(ISuggestionProvider), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
|
||||
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null, OnSelectedItemChanged));
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty, propertyChangedCallback:null,coerceValueCallback:null, isAnimationProhibited:false, defaultUpdateSourceTrigger: UpdateSourceTrigger.LostFocus, flags: FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||
public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(0));
|
||||
public static readonly DependencyProperty CharacterCasingProperty = DependencyProperty.Register("CharacterCasing", typeof(CharacterCasing), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(CharacterCasing.Normal));
|
||||
public static readonly DependencyProperty MaxPopUpHeightProperty = DependencyProperty.Register("MaxPopUpHeight", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(600));
|
||||
public static readonly DependencyProperty MaxPopUpWidthProperty = DependencyProperty.Register("MaxPopUpWidth", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(2000));
|
||||
|
||||
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register("Watermark", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
|
||||
|
||||
public static readonly DependencyProperty SuggestionBackgroundProperty = DependencyProperty.Register("SuggestionBackground", typeof(Brush), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(Brushes.White));
|
||||
private bool _isUpdatingText;
|
||||
private bool _selectionCancelled;
|
||||
|
||||
private SuggestionsAdapter _suggestionsAdapter;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Constructors"
|
||||
|
||||
static AutoCompleteTextBox()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(typeof(AutoCompleteTextBox)));
|
||||
FocusableProperty.OverrideMetadata(typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(true));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties"
|
||||
|
||||
|
||||
public int MaxPopupHeight
|
||||
{
|
||||
get => (int)GetValue(MaxPopUpHeightProperty);
|
||||
set => SetValue(MaxPopUpHeightProperty, value);
|
||||
}
|
||||
public int MaxPopupWidth
|
||||
{
|
||||
get => (int)GetValue(MaxPopUpWidthProperty);
|
||||
set => SetValue(MaxPopUpWidthProperty, value);
|
||||
}
|
||||
|
||||
public BindingEvaluator BindingEvaluator { get; set; }
|
||||
|
||||
public CharacterCasing CharacterCasing
|
||||
{
|
||||
get => (CharacterCasing)GetValue(CharacterCasingProperty);
|
||||
set => SetValue(CharacterCasingProperty, value);
|
||||
}
|
||||
|
||||
public int MaxLength
|
||||
{
|
||||
get => (int)GetValue(MaxLengthProperty);
|
||||
set => SetValue(MaxLengthProperty, value);
|
||||
}
|
||||
|
||||
public int Delay
|
||||
{
|
||||
get => (int)GetValue(DelayProperty);
|
||||
|
||||
set => SetValue(DelayProperty, value);
|
||||
}
|
||||
|
||||
public string DisplayMember
|
||||
{
|
||||
get => (string)GetValue(DisplayMemberProperty);
|
||||
|
||||
set => SetValue(DisplayMemberProperty, value);
|
||||
}
|
||||
|
||||
public TextBox Editor { get; set; }
|
||||
|
||||
public DispatcherTimer FetchTimer { get; set; }
|
||||
|
||||
public string Filter
|
||||
{
|
||||
get => (string)GetValue(FilterProperty);
|
||||
|
||||
set => SetValue(FilterProperty, value);
|
||||
}
|
||||
|
||||
public object Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public IconPlacement IconPlacement
|
||||
{
|
||||
get => (IconPlacement)GetValue(IconPlacementProperty);
|
||||
|
||||
set => SetValue(IconPlacementProperty, value);
|
||||
}
|
||||
|
||||
public Visibility IconVisibility
|
||||
{
|
||||
get => (Visibility)GetValue(IconVisibilityProperty);
|
||||
|
||||
set => SetValue(IconVisibilityProperty, value);
|
||||
}
|
||||
|
||||
public bool IsDropDownOpen
|
||||
{
|
||||
get => (bool)GetValue(IsDropDownOpenProperty);
|
||||
|
||||
set => SetValue(IsDropDownOpenProperty, value);
|
||||
}
|
||||
|
||||
public bool IsLoading
|
||||
{
|
||||
get => (bool)GetValue(IsLoadingProperty);
|
||||
|
||||
set => SetValue(IsLoadingProperty, value);
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get => (bool)GetValue(IsReadOnlyProperty);
|
||||
|
||||
set => SetValue(IsReadOnlyProperty, value);
|
||||
}
|
||||
|
||||
public Selector ItemsSelector { get; set; }
|
||||
|
||||
public DataTemplate ItemTemplate
|
||||
{
|
||||
get => (DataTemplate)GetValue(ItemTemplateProperty);
|
||||
|
||||
set => SetValue(ItemTemplateProperty, value);
|
||||
}
|
||||
|
||||
public DataTemplateSelector ItemTemplateSelector
|
||||
{
|
||||
get => ((DataTemplateSelector)(GetValue(ItemTemplateSelectorProperty)));
|
||||
set => SetValue(ItemTemplateSelectorProperty, value);
|
||||
}
|
||||
|
||||
public object LoadingContent
|
||||
{
|
||||
get => GetValue(LoadingContentProperty);
|
||||
|
||||
set => SetValue(LoadingContentProperty, value);
|
||||
}
|
||||
|
||||
public Popup Popup { get; set; }
|
||||
|
||||
public ISuggestionProvider Provider
|
||||
{
|
||||
get => (ISuggestionProvider)GetValue(ProviderProperty);
|
||||
|
||||
set => SetValue(ProviderProperty, value);
|
||||
}
|
||||
|
||||
public object SelectedItem
|
||||
{
|
||||
get => GetValue(SelectedItemProperty);
|
||||
|
||||
set => SetValue(SelectedItemProperty, value);
|
||||
}
|
||||
|
||||
public SelectionAdapter SelectionAdapter { get; set; }
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public string Watermark
|
||||
{
|
||||
get => (string)GetValue(WatermarkProperty);
|
||||
|
||||
set => SetValue(WatermarkProperty, value);
|
||||
}
|
||||
public Brush SuggestionBackground
|
||||
{
|
||||
get => (Brush)GetValue(SuggestionBackgroundProperty);
|
||||
|
||||
set => SetValue(SuggestionBackgroundProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods"
|
||||
|
||||
public static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
AutoCompleteTextBox act = null;
|
||||
act = d as AutoCompleteTextBox;
|
||||
if (act != null)
|
||||
{
|
||||
if (act.Editor != null & !act._isUpdatingText)
|
||||
{
|
||||
act._isUpdatingText = true;
|
||||
act.Editor.Text = act.BindingEvaluator.Evaluate(e.NewValue);
|
||||
act._isUpdatingText = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToSelectedItem()
|
||||
{
|
||||
if (ItemsSelector is ListBox listBox && listBox.SelectedItem != null)
|
||||
listBox.ScrollIntoView(listBox.SelectedItem);
|
||||
}
|
||||
|
||||
public new BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding){
|
||||
var res = base.SetBinding(dp, binding);
|
||||
CheckForParentTextBindingChange();
|
||||
return res;
|
||||
}
|
||||
public new BindingExpressionBase SetBinding(DependencyProperty dp, String path) {
|
||||
var res = base.SetBinding(dp, path);
|
||||
CheckForParentTextBindingChange();
|
||||
return res;
|
||||
}
|
||||
public new void ClearValue(DependencyPropertyKey key) {
|
||||
base.ClearValue(key);
|
||||
CheckForParentTextBindingChange();
|
||||
}
|
||||
public new void ClearValue(DependencyProperty dp) {
|
||||
base.ClearValue(dp);
|
||||
CheckForParentTextBindingChange();
|
||||
}
|
||||
private void CheckForParentTextBindingChange(bool force=false) {
|
||||
var CurrentBindingMode = BindingOperations.GetBinding(this, TextProperty)?.UpdateSourceTrigger ?? UpdateSourceTrigger.Default;
|
||||
if (CurrentBindingMode != UpdateSourceTrigger.PropertyChanged)//preventing going any less frequent than property changed
|
||||
CurrentBindingMode = UpdateSourceTrigger.Default;
|
||||
|
||||
|
||||
if (CurrentBindingMode == CurrentTextboxTextBindingUpdateMode && force == false)
|
||||
return;
|
||||
var binding = new Binding {
|
||||
Mode = BindingMode.TwoWay,
|
||||
UpdateSourceTrigger = CurrentBindingMode,
|
||||
Path = new PropertyPath(nameof(Text)),
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
|
||||
};
|
||||
CurrentTextboxTextBindingUpdateMode = CurrentBindingMode;
|
||||
Editor?.SetBinding(TextBox.TextProperty, binding);
|
||||
}
|
||||
|
||||
private UpdateSourceTrigger CurrentTextboxTextBindingUpdateMode;
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
Editor = Template.FindName(PartEditor, this) as TextBox;
|
||||
Editor.Focus();
|
||||
Popup = Template.FindName(PartPopup, this) as Popup;
|
||||
ItemsSelector = Template.FindName(PartSelector, this) as Selector;
|
||||
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
|
||||
|
||||
if (Editor != null)
|
||||
{
|
||||
Editor.TextChanged += OnEditorTextChanged;
|
||||
Editor.PreviewKeyDown += OnEditorKeyDown;
|
||||
Editor.LostFocus += OnEditorLostFocus;
|
||||
CheckForParentTextBindingChange(true);
|
||||
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
_isUpdatingText = true;
|
||||
Editor.Text = BindingEvaluator.Evaluate(SelectedItem);
|
||||
_isUpdatingText = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GotFocus += AutoCompleteTextBox_GotFocus;
|
||||
GotKeyboardFocus += AutoCompleteTextBox_GotKeyboardFocus;
|
||||
|
||||
if (Popup != null)
|
||||
{
|
||||
Popup.StaysOpen = false;
|
||||
Popup.Opened += OnPopupOpened;
|
||||
Popup.Closed += OnPopupClosed;
|
||||
}
|
||||
if (ItemsSelector != null)
|
||||
{
|
||||
SelectionAdapter = new SelectionAdapter(ItemsSelector);
|
||||
SelectionAdapter.Commit += OnSelectionAdapterCommit;
|
||||
SelectionAdapter.Cancel += OnSelectionAdapterCancel;
|
||||
SelectionAdapter.SelectionChanged += OnSelectionAdapterSelectionChanged;
|
||||
ItemsSelector.PreviewMouseDown += ItemsSelector_PreviewMouseDown;
|
||||
}
|
||||
}
|
||||
private void ItemsSelector_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if ((e.OriginalSource as FrameworkElement)?.DataContext == null)
|
||||
return;
|
||||
if (!ItemsSelector.Items.Contains(((FrameworkElement)e.OriginalSource)?.DataContext))
|
||||
return;
|
||||
ItemsSelector.SelectedItem = ((FrameworkElement)e.OriginalSource)?.DataContext;
|
||||
OnSelectionAdapterCommit(SelectionAdapter.EventCause.MouseDown);
|
||||
e.Handled = true;
|
||||
}
|
||||
private void AutoCompleteTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Editor?.Focus();
|
||||
}
|
||||
private void AutoCompleteTextBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
|
||||
if (e.NewFocus != this)
|
||||
return;
|
||||
if (e.OldFocus == Editor)
|
||||
MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
|
||||
|
||||
}
|
||||
|
||||
private string GetDisplayText(object dataItem)
|
||||
{
|
||||
if (BindingEvaluator == null)
|
||||
{
|
||||
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
|
||||
}
|
||||
if (dataItem == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (string.IsNullOrEmpty(DisplayMember))
|
||||
{
|
||||
return dataItem.ToString();
|
||||
}
|
||||
return BindingEvaluator.Evaluate(dataItem);
|
||||
}
|
||||
|
||||
private void OnEditorKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (SelectionAdapter != null)
|
||||
{
|
||||
if (IsDropDownOpen)
|
||||
SelectionAdapter.HandleKeyDown(e);
|
||||
else
|
||||
IsDropDownOpen = e.Key == Key.Down || e.Key == Key.Up;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEditorLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!IsKeyboardFocusWithin)
|
||||
{
|
||||
IsDropDownOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEditorTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
Text = Editor.Text;
|
||||
if (_isUpdatingText)
|
||||
return;
|
||||
if (FetchTimer == null)
|
||||
{
|
||||
FetchTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(Delay) };
|
||||
FetchTimer.Tick += OnFetchTimerTick;
|
||||
}
|
||||
FetchTimer.IsEnabled = false;
|
||||
FetchTimer.Stop();
|
||||
SetSelectedItem(null);
|
||||
if (Editor.Text.Length > 0)
|
||||
{
|
||||
FetchTimer.IsEnabled = true;
|
||||
FetchTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
IsDropDownOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFetchTimerTick(object sender, EventArgs e)
|
||||
{
|
||||
FetchTimer.IsEnabled = false;
|
||||
FetchTimer.Stop();
|
||||
if (Provider != null && ItemsSelector != null)
|
||||
{
|
||||
Filter = Editor.Text;
|
||||
if (_suggestionsAdapter == null)
|
||||
{
|
||||
_suggestionsAdapter = new SuggestionsAdapter(this);
|
||||
}
|
||||
_suggestionsAdapter.GetSuggestions(Filter);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPopupClosed(object sender, EventArgs e)
|
||||
{
|
||||
if (!_selectionCancelled)
|
||||
{
|
||||
OnSelectionAdapterCommit(SelectionAdapter.EventCause.PopupClosed);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPopupOpened(object sender, EventArgs e)
|
||||
{
|
||||
_selectionCancelled = false;
|
||||
ItemsSelector.SelectedItem = SelectedItem;
|
||||
}
|
||||
|
||||
private void OnSelectionAdapterCancel(SelectionAdapter.EventCause cause)
|
||||
{
|
||||
if (PreSelectionEventSomeoneHandled(cause, true))
|
||||
return;
|
||||
|
||||
_isUpdatingText = true;
|
||||
Editor.Text = SelectedItem == null ? Filter : GetDisplayText(SelectedItem);
|
||||
Editor.SelectionStart = Editor.Text.Length;
|
||||
Editor.SelectionLength = 0;
|
||||
_isUpdatingText = false;
|
||||
IsDropDownOpen = false;
|
||||
_selectionCancelled = true;
|
||||
|
||||
}
|
||||
|
||||
public event EventHandler<SelectionAdapter.PreSelectionAdapterFinishArgs> PreSelectionAdapterFinish;
|
||||
private bool PreSelectionEventSomeoneHandled(SelectionAdapter.EventCause cause, bool is_cancel) {
|
||||
if (PreSelectionAdapterFinish == null)
|
||||
return false;
|
||||
var args = new SelectionAdapter.PreSelectionAdapterFinishArgs { cause = cause, is_cancel = is_cancel };
|
||||
PreSelectionAdapterFinish?.Invoke(this, args);
|
||||
return args.handled;
|
||||
|
||||
}
|
||||
private void OnSelectionAdapterCommit(SelectionAdapter.EventCause cause)
|
||||
{
|
||||
if (PreSelectionEventSomeoneHandled(cause, false))
|
||||
return;
|
||||
|
||||
if (ItemsSelector.SelectedItem != null)
|
||||
{
|
||||
SelectedItem = ItemsSelector.SelectedItem;
|
||||
_isUpdatingText = true;
|
||||
Editor.Text = GetDisplayText(ItemsSelector.SelectedItem);
|
||||
SetSelectedItem(ItemsSelector.SelectedItem);
|
||||
_isUpdatingText = false;
|
||||
IsDropDownOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionAdapterSelectionChanged()
|
||||
{
|
||||
_isUpdatingText = true;
|
||||
Editor.Text = ItemsSelector.SelectedItem == null ? Filter : GetDisplayText(ItemsSelector.SelectedItem);
|
||||
Editor.SelectionStart = Editor.Text.Length;
|
||||
Editor.SelectionLength = 0;
|
||||
ScrollToSelectedItem();
|
||||
_isUpdatingText = false;
|
||||
}
|
||||
|
||||
private void SetSelectedItem(object item)
|
||||
{
|
||||
_isUpdatingText = true;
|
||||
SelectedItem = item;
|
||||
_isUpdatingText = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Nested Types"
|
||||
|
||||
private class SuggestionsAdapter
|
||||
{
|
||||
|
||||
#region "Fields"
|
||||
|
||||
private readonly AutoCompleteTextBox _actb;
|
||||
|
||||
private string _filter;
|
||||
#endregion
|
||||
|
||||
#region "Constructors"
|
||||
|
||||
public SuggestionsAdapter(AutoCompleteTextBox actb)
|
||||
{
|
||||
_actb = actb;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods"
|
||||
|
||||
public void GetSuggestions(string searchText)
|
||||
{
|
||||
_filter = searchText;
|
||||
_actb.IsLoading = true;
|
||||
// Do not open drop down if control is not focused
|
||||
if (_actb.IsKeyboardFocusWithin)
|
||||
_actb.IsDropDownOpen = true;
|
||||
_actb.ItemsSelector.ItemsSource = null;
|
||||
ParameterizedThreadStart thInfo = GetSuggestionsAsync;
|
||||
Thread th = new Thread(thInfo);
|
||||
th.Start(new object[] { searchText, _actb.Provider });
|
||||
}
|
||||
|
||||
private void DisplaySuggestions(IEnumerable suggestions, string filter)
|
||||
{
|
||||
if (_filter != filter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_actb.IsLoading = false;
|
||||
_actb.ItemsSelector.ItemsSource = suggestions;
|
||||
// Close drop down if there are no items
|
||||
if (_actb.IsDropDownOpen)
|
||||
{
|
||||
_actb.IsDropDownOpen = _actb.ItemsSelector.HasItems;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetSuggestionsAsync(object param)
|
||||
{
|
||||
if (param is object[] args)
|
||||
{
|
||||
string searchText = Convert.ToString(args[0]);
|
||||
if (args[1] is ISuggestionProvider provider)
|
||||
{
|
||||
IEnumerable list = provider.GetSuggestions(searchText);
|
||||
_actb.Dispatcher.BeginInvoke(new Action<IEnumerable, string>(DisplaySuggestions), DispatcherPriority.Background, list, searchText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
16
AutoCompleteTextBox/Editors/IComboSuggestionProvider.cs
Normal file
16
AutoCompleteTextBox/Editors/IComboSuggestionProvider.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections;
|
||||
|
||||
|
||||
namespace AutoCompleteTextBox.Editors
|
||||
{
|
||||
public interface IComboSuggestionProvider
|
||||
{
|
||||
|
||||
#region Public Methods
|
||||
|
||||
IEnumerable GetSuggestions(string filter);
|
||||
IEnumerable GetFullCollection();
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
||||
15
AutoCompleteTextBox/Editors/ISuggestionProvider.cs
Normal file
15
AutoCompleteTextBox/Editors/ISuggestionProvider.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace AutoCompleteTextBox.Editors
|
||||
{
|
||||
public interface ISuggestionProvider
|
||||
{
|
||||
|
||||
#region Public Methods
|
||||
|
||||
IEnumerable GetSuggestions(string filter);
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
}
|
||||
}
|
||||
122
AutoCompleteTextBox/Editors/SelectionAdapter.cs
Normal file
122
AutoCompleteTextBox/Editors/SelectionAdapter.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace AutoCompleteTextBox.Editors
|
||||
{
|
||||
public class SelectionAdapter
|
||||
{
|
||||
public class PreSelectionAdapterFinishArgs {
|
||||
public EventCause cause;
|
||||
public bool is_cancel;
|
||||
public bool handled;
|
||||
}
|
||||
|
||||
#region "Fields"
|
||||
#endregion
|
||||
|
||||
#region "Constructors"
|
||||
|
||||
public SelectionAdapter(Selector selector)
|
||||
{
|
||||
SelectorControl = selector;
|
||||
SelectorControl.PreviewMouseUp += OnSelectorMouseDown;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Events"
|
||||
|
||||
public enum EventCause { Other, PopupClosed, ItemClicked, EnterPressed, EscapePressed, TabPressed, MouseDown}
|
||||
public delegate void CancelEventHandler(EventCause cause);
|
||||
|
||||
public delegate void CommitEventHandler(EventCause cause);
|
||||
|
||||
public delegate void SelectionChangedEventHandler();
|
||||
|
||||
public event CancelEventHandler Cancel;
|
||||
public event CommitEventHandler Commit;
|
||||
public event SelectionChangedEventHandler SelectionChanged;
|
||||
#endregion
|
||||
|
||||
#region "Properties"
|
||||
|
||||
public Selector SelectorControl { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods"
|
||||
|
||||
public void HandleKeyDown(KeyEventArgs key)
|
||||
{
|
||||
switch (key.Key)
|
||||
{
|
||||
case Key.Down:
|
||||
IncrementSelection();
|
||||
break;
|
||||
case Key.Up:
|
||||
DecrementSelection();
|
||||
break;
|
||||
case Key.Enter:
|
||||
Commit?.Invoke(EventCause.EnterPressed);
|
||||
|
||||
break;
|
||||
case Key.Escape:
|
||||
Cancel?.Invoke(EventCause.EscapePressed);
|
||||
|
||||
break;
|
||||
case Key.Tab:
|
||||
Commit?.Invoke(EventCause.TabPressed);
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
key.Handled = true;
|
||||
}
|
||||
|
||||
private void DecrementSelection()
|
||||
{
|
||||
if (SelectorControl.SelectedIndex == -1)
|
||||
{
|
||||
SelectorControl.SelectedIndex = SelectorControl.Items.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectorControl.SelectedIndex -= 1;
|
||||
}
|
||||
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void IncrementSelection()
|
||||
{
|
||||
if (SelectorControl.SelectedIndex == SelectorControl.Items.Count - 1)
|
||||
{
|
||||
SelectorControl.SelectedIndex = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectorControl.SelectedIndex += 1;
|
||||
}
|
||||
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void OnSelectorMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// If sender is the RepeatButton from the scrollbar we need to
|
||||
// to skip this event otherwise focus get stuck in the RepeatButton
|
||||
// and list is scrolled up or down til the end.
|
||||
if (e.OriginalSource.GetType() != typeof(RepeatButton))
|
||||
{
|
||||
Commit?.Invoke(EventCause.MouseDown);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
35
AutoCompleteTextBox/Editors/SuggestionProvider.cs
Normal file
35
AutoCompleteTextBox/Editors/SuggestionProvider.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace AutoCompleteTextBox.Editors
|
||||
{
|
||||
public class SuggestionProvider : ISuggestionProvider
|
||||
{
|
||||
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private readonly Func<string, IEnumerable> _method;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public SuggestionProvider(Func<string, IEnumerable> method)
|
||||
{
|
||||
_method = method ?? throw new ArgumentNullException(nameof(method));
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public IEnumerable GetSuggestions(string filter)
|
||||
{
|
||||
return _method(filter);
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
}
|
||||
}
|
||||
268
AutoCompleteTextBox/Editors/Themes/Generic.xaml
Normal file
268
AutoCompleteTextBox/Editors/Themes/Generic.xaml
Normal file
@@ -0,0 +1,268 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:editors="clr-namespace:AutoCompleteTextBox.Editors">
|
||||
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
|
||||
|
||||
<Style x:Key="TextBoxSuggestionItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="ContentBorder" Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteTextBox}, Mode=OneWay}">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="IsSelected" Value="True" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="ContentBorder" Property="Background" Value="{x:Static SystemColors.HighlightBrush}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{x:Static SystemColors.HighlightTextBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ComboBoxSuggestionItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="ContentBorder" Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteComboBox}, Mode=OneWay}">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="IsSelected" Value="True" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="ContentBorder" Property="Background" Value="{x:Static SystemColors.HighlightBrush}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{x:Static SystemColors.HighlightTextBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="TransparentTextBoxStyle" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TextBox}">
|
||||
<Grid>
|
||||
<ScrollViewer
|
||||
x:Name="PART_ContentHost"
|
||||
Background="Transparent"
|
||||
CanContentScroll="True"
|
||||
Focusable="True"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type editors:AutoCompleteTextBox}">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="#FFABADB3" />
|
||||
<Setter Property="SuggestionBackground" Value="White" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Top" />
|
||||
<Setter Property="AllowDrop" Value="true" />
|
||||
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type editors:AutoCompleteTextBox}">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0">
|
||||
<Grid>
|
||||
<DockPanel>
|
||||
<ContentPresenter
|
||||
x:Name="PART_Icon"
|
||||
ContentSource="Icon"
|
||||
Visibility="{TemplateBinding IconVisibility}" />
|
||||
<Grid>
|
||||
<TextBlock
|
||||
x:Name="PART_Watermark"
|
||||
Margin="3,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
Foreground="Gray"
|
||||
Text="{TemplateBinding Watermark}"
|
||||
Visibility="Collapsed" />
|
||||
<TextBox
|
||||
x:Name="PART_Editor"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
CharacterCasing="{Binding Path=CharacterCasing, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=OneWay}"
|
||||
MaxLength="{Binding Path=MaxLength, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
Style="{StaticResource ResourceKey=TransparentTextBoxStyle}" />
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
<Popup
|
||||
x:Name="PART_Popup"
|
||||
MinWidth="{TemplateBinding ActualWidth}"
|
||||
MinHeight="25"
|
||||
MaxHeight="600"
|
||||
AllowsTransparency="True"
|
||||
Focusable="False"
|
||||
HorizontalOffset="0"
|
||||
IsOpen="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
PopupAnimation="Slide">
|
||||
<Border
|
||||
Padding="2"
|
||||
Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteTextBox}, Mode=OneWay}"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
CornerRadius="5">
|
||||
<Grid Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteTextBox}, Mode=OneWay}">
|
||||
<ListBox
|
||||
x:Name="PART_Selector"
|
||||
MaxWidth="{Binding Path=MaxPopupWidth, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
MaxHeight="{Binding Path=MaxPopupHeight, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteTextBox}, Mode=OneWay}"
|
||||
BorderThickness="0"
|
||||
Focusable="False"
|
||||
ItemContainerStyle="{StaticResource ResourceKey=TextBoxSuggestionItemStyle}"
|
||||
ItemTemplate="{TemplateBinding ItemTemplate}"
|
||||
ItemTemplateSelector="{TemplateBinding ItemTemplateSelector}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto" />
|
||||
<Border Visibility="{Binding Path=IsLoading, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource ResourceKey=BoolToVisConverter}}">
|
||||
<ContentPresenter ContentSource="LoadingContent" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger SourceName="PART_Editor" Property="Text" Value="">
|
||||
<Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible" />
|
||||
</Trigger>
|
||||
<Trigger Property="IconPlacement" Value="Left">
|
||||
<Setter TargetName="PART_Icon" Property="DockPanel.Dock" Value="Left" />
|
||||
</Trigger>
|
||||
<Trigger Property="IconPlacement" Value="Right">
|
||||
<Setter TargetName="PART_Icon" Property="DockPanel.Dock" Value="Right" />
|
||||
</Trigger>
|
||||
<Trigger Property="Validation.HasError" Value="True">
|
||||
<Setter Property="BorderBrush" Value="Red" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="editors:AutoCompleteComboBox">
|
||||
<Setter Property="Focusable" Value="True" />
|
||||
<Setter Property="SuggestionBackground" Value="White" />
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Top" />
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="AllowDrop" Value="true" />
|
||||
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type editors:AutoCompleteComboBox}">
|
||||
<Grid>
|
||||
<DockPanel>
|
||||
<ContentPresenter
|
||||
x:Name="PART_Icon"
|
||||
ContentSource="Icon"
|
||||
Visibility="{TemplateBinding IconVisibility}" />
|
||||
<Grid>
|
||||
<TextBlock
|
||||
x:Name="PART_Watermark"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Left"
|
||||
Focusable="False"
|
||||
Foreground="Gray"
|
||||
Text="{TemplateBinding Watermark}"
|
||||
Visibility="Collapsed" />
|
||||
<DockPanel Margin="3,0">
|
||||
<Expander x:Name="PART_Expander" DockPanel.Dock="Right" />
|
||||
<TextBox
|
||||
x:Name="PART_Editor"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
CharacterCasing="{Binding Path=CharacterCasing, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
Focusable="True"
|
||||
MaxLength="{Binding Path=MaxLength, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}" />
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
<Popup
|
||||
x:Name="PART_Popup"
|
||||
MinWidth="{TemplateBinding ActualWidth}"
|
||||
MinHeight="25"
|
||||
MaxHeight="600"
|
||||
AllowsTransparency="True"
|
||||
Focusable="False"
|
||||
HorizontalOffset="0"
|
||||
IsOpen="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
PopupAnimation="Slide">
|
||||
<Border
|
||||
Padding="2"
|
||||
Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteComboBox}, Mode=OneWay}"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
CornerRadius="5">
|
||||
<Grid>
|
||||
<ListBox
|
||||
x:Name="PART_Selector"
|
||||
MaxWidth="{Binding Path=MaxPopupWidth, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
MaxHeight="{Binding Path=MaxPopupHeight, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||
Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteComboBox}, Mode=OneWay}"
|
||||
BorderThickness="0"
|
||||
Focusable="False"
|
||||
ItemContainerStyle="{StaticResource ResourceKey=ComboBoxSuggestionItemStyle}"
|
||||
ItemTemplate="{TemplateBinding ItemTemplate}"
|
||||
ItemTemplateSelector="{TemplateBinding ItemTemplateSelector}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto" />
|
||||
<Border Visibility="{Binding Path=IsLoading, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource ResourceKey=BoolToVisConverter}}">
|
||||
<ContentPresenter ContentSource="LoadingContent" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
|
||||
</Grid>
|
||||
<!--</Border>-->
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger SourceName="PART_Editor" Property="Text" Value="">
|
||||
<Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible" />
|
||||
</Trigger>
|
||||
<Trigger Property="IconPlacement" Value="Left">
|
||||
<Setter TargetName="PART_Icon" Property="DockPanel.Dock" Value="Left" />
|
||||
</Trigger>
|
||||
<Trigger Property="IconPlacement" Value="Right">
|
||||
<Setter TargetName="PART_Icon" Property="DockPanel.Dock" Value="Right" />
|
||||
</Trigger>
|
||||
<Trigger Property="Validation.HasError" Value="True">
|
||||
<Setter Property="BorderBrush" Value="Red" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
8
AutoCompleteTextBox/Enumerations.cs
Normal file
8
AutoCompleteTextBox/Enumerations.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace AutoCompleteTextBox
|
||||
{
|
||||
public enum IconPlacement
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
53
AutoCompleteTextBox/Properties/AssemblyInfo.cs
Normal file
53
AutoCompleteTextBox/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Markup;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("AutoCompleteTextBox")]
|
||||
[assembly: AssemblyDescription("An autocomplete textbox for WPF")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("AutoCompleteTextBox")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
//In order to begin building localizable applications, set
|
||||
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
||||
//inside a <PropertyGroup>. For example, if you are using US english
|
||||
//in your source files, set the <UICulture> to en-US. Then uncomment
|
||||
//the NeutralResourceLanguage attribute below. Update the "en-US" in
|
||||
//the line below to match the UICulture setting in the project file.
|
||||
|
||||
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
|
||||
|
||||
|
||||
[assembly:ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.1.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.1.0")]
|
||||
[assembly: XmlnsDefinition("http://wpfcontrols.com/", "AutoCompleteTextBox")]
|
||||
[assembly: XmlnsDefinition("http://wpfcontrols.com/", "AutoCompleteTextBox.Editors")]
|
||||
63
AutoCompleteTextBox/Properties/Resources.Designer.cs
generated
Normal file
63
AutoCompleteTextBox/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace AutoCompleteTextBox.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoCompleteTextBox.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
AutoCompleteTextBox/Properties/Resources.resx
Normal file
117
AutoCompleteTextBox/Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
26
AutoCompleteTextBox/Properties/Settings.Designer.cs
generated
Normal file
26
AutoCompleteTextBox/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace AutoCompleteTextBox.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
AutoCompleteTextBox/Properties/Settings.settings
Normal file
7
AutoCompleteTextBox/Properties/Settings.settings
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
6
AutoCompleteTextBox/Themes/Generic.xaml
Normal file
6
AutoCompleteTextBox/Themes/Generic.xaml
Normal file
@@ -0,0 +1,6 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/AutoCompleteTextBox;component/editors/themes/generic.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user