using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace Developpez.Dotnet.Windows.Behaviors
{
///
/// Fournit des propriétés attachées pour gérer l'autocomplétion dans une TextBox
///
public static class AutoCompleteBehavior
{
#region Attached properties
///
/// Identifiant de la propriété attachée ItemsSource
///
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached(
"ItemsSource",
typeof(IEnumerable),
typeof(AutoCompleteBehavior),
new UIPropertyMetadata(
null,
ItemsSourceChanged));
///
/// Identifiant de la propriété attachée DisplayMemberPath
///
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.RegisterAttached("DisplayMemberPath", typeof(string), typeof(AutoCompleteBehavior), new UIPropertyMetadata(string.Empty));
///
/// Identifiant de la propriété attachée SearchMemberPath
///
public static readonly DependencyProperty SearchMemberPathProperty =
DependencyProperty.RegisterAttached("SearchMemberPath", typeof(string), typeof(AutoCompleteBehavior), new UIPropertyMetadata(string.Empty));
///
/// Identifiant de la propriété attachée ComparisonType
///
public static readonly DependencyProperty ComparisonTypeProperty =
DependencyProperty.RegisterAttached("ComparisonType", typeof(StringComparison), typeof(AutoCompleteBehavior), new UIPropertyMetadata(StringComparison.CurrentCultureIgnoreCase));
///
/// Identifiant de la propriété attachée Suggest
///
public static readonly DependencyProperty SuggestProperty =
DependencyProperty.RegisterAttached("Suggest", typeof(bool), typeof(AutoCompleteBehavior), new UIPropertyMetadata(false));
///
/// Identifiant de la propriété attachée MatchMode
///
public static readonly DependencyProperty MatchModeProperty =
DependencyProperty.RegisterAttached("MatchMode", typeof(StringMatchMode), typeof(AutoCompleteBehavior), new UIPropertyMetadata(StringMatchMode.StartsWith));
///
/// Identifiant de la propriété attachée ItemTemplate
///
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.RegisterAttached("ItemTemplate", typeof(DataTemplate), typeof(AutoCompleteBehavior), new UIPropertyMetadata(null));
private static readonly DependencyPropertyKey AutoCompletePopupPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("AutoCompletePopup", typeof(AutoCompletePopup), typeof(AutoCompleteBehavior), new UIPropertyMetadata(null));
#endregion
#region Attached property accessors
///
/// Obtient la valeur de la propriété attachée ItemsSource pour l'élément spécifié.
///
/// L'objet pour lequel on veut obtenir la valeur de la propriété
/// La liste utilisée pour l'autocomplétion
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static IEnumerable GetItemsSource(TextBox obj)
{
return (IEnumerable)obj.GetValue(ItemsSourceProperty);
}
///
/// Définit la valeur de la propriété attachée ItemsSource pour l'élément spécifié.
///
/// L'objet pour lequel on veut définir la valeur de la propriété
/// La liste à utiliser pour l'autocomplétion
public static void SetItemsSource(TextBox obj, IEnumerable value)
{
obj.SetValue(ItemsSourceProperty, value);
}
///
/// Obtient la valeur de la propriété attachée ItemTemplate pour l'élément spécifié.
///
/// L'objet pour lequel on veut obtenir la valeur de la propriété
/// Le DataTemplate utilisé pour les éléments de la liste
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static DataTemplate GetItemTemplate(DependencyObject obj)
{
return (DataTemplate)obj.GetValue(ItemTemplateProperty);
}
///
/// Définit la valeur de la propriété attachée ItemTemplate pour l'élément spécifié.
///
/// L'objet pour lequel on veut définir la valeur de la propriété
/// Le DataTemplate à utiliser pour les éléments de la liste
public static void SetItemTemplate(DependencyObject obj, DataTemplate value)
{
obj.SetValue(ItemTemplateProperty, value);
}
///
/// Obtient la valeur de la propriété attachée DisplayMemberPath pour l'élément spécifié.
///
/// L'objet pour lequel on veut obtenir la valeur de la propriété
/// Le membre des éléments de la liste à afficher
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static string GetDisplayMemberPath(DependencyObject obj)
{
return (string)obj.GetValue(DisplayMemberPathProperty);
}
///
/// Définit la valeur de la propriété attachée DisplayMemberPath pour l'élément spécifié.
///
/// L'objet pour lequel on veut définir la valeur de la propriété
/// Le membre des éléments de la liste à afficher
public static void SetDisplayMemberPath(DependencyObject obj, string value)
{
obj.SetValue(DisplayMemberPathProperty, value);
}
///
/// Obtient la valeur de la propriété attachée SearchMemberPath pour l'élément spécifié.
///
/// L'objet pour lequel on veut obtenir la valeur de la propriété
/// Le membre des éléments de la liste utilisé pour la recherche
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static string GetSearchMemberPath(DependencyObject obj)
{
return (string)obj.GetValue(SearchMemberPathProperty);
}
///
/// Définit la valeur de la propriété attachée SearchMemberPath pour l'élément spécifié.
///
/// L'objet pour lequel on veut définir la valeur de la propriété
/// Le membre des éléments de la liste à utiliser pour la recherche
public static void SetSearchMemberPath(DependencyObject obj, string value)
{
obj.SetValue(SearchMemberPathProperty, value);
}
///
/// Obtient la valeur de la propriété attachée Suggest pour l'élément spécifié.
///
/// L'objet pour lequel on veut obtenir la valeur de la propriété
/// True si la suite du texte est suggérée automatiquement, sinon false
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetSuggest(DependencyObject obj)
{
return (bool)obj.GetValue(SuggestProperty);
}
///
/// Définit la valeur de la propriété attachée Suggest pour l'élément spécifié.
///
/// L'objet pour lequel on veut définir la valeur de la propriété
/// True pour suggérer automatiquement la suite du texte, sinon false
public static void SetSuggest(DependencyObject obj, bool value)
{
obj.SetValue(SuggestProperty, value);
}
///
/// Obtient la valeur de la propriété attachée MatchMode pour l'élément spécifié.
///
/// L'objet pour lequel on veut obtenir la valeur de la propriété
/// Le mode de correspondance de chaine utilisé pour la recherche
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static StringMatchMode GetMatchMode(DependencyObject obj)
{
return (StringMatchMode)obj.GetValue(MatchModeProperty);
}
///
/// Définit la valeur de la propriété attachée MatchMode pour l'élément spécifié.
///
/// L'objet pour lequel on veut définir la valeur de la propriété
/// Le mode de correspondance de chaine à utiliser pour la recherche
public static void SetMatchMode(DependencyObject obj, StringMatchMode value)
{
obj.SetValue(MatchModeProperty, value);
}
///
/// Obtient la valeur de la propriété attachée ComparisonType pour l'élément spécifié.
///
/// L'objet pour lequel on veut obtenir la valeur de la propriété
/// Le type de comparaison de chaine pour le filtrage
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static StringComparison GetComparisonType(DependencyObject obj)
{
return (StringComparison)obj.GetValue(ComparisonTypeProperty);
}
///
/// Définit la valeur de la propriété attachée ComparisonType pour l'élément spécifié.
///
/// L'objet pour lequel on veut définir la valeur de la propriété
/// Le type de comparaison de chaine pour le filtrage
public static void SetComparisonType(DependencyObject obj, StringComparison value)
{
obj.SetValue(ComparisonTypeProperty, value);
}
#endregion
private static void ItemsSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
TextBox textBox = o as TextBox;
if (textBox == null)
return;
if (e.OldValue != null && e.NewValue == null)
{
var popup = (AutoCompletePopup)textBox.GetValue(AutoCompletePopupPropertyKey.DependencyProperty);
if (popup != null)
popup.Dispose();
}
else if (e.OldValue == null && e.NewValue != null)
{
new AutoCompletePopup(textBox);
}
}
private class AutoCompletePopup : Popup, IDisposable
{
private readonly ListBox _listBox;
private readonly TextBox _textBox;
private string _currentSearch;
private bool _textChanging;
private bool _deleting;
public AutoCompletePopup(TextBox textBox)
{
_textBox = textBox;
SetBinding(
MinWidthProperty,
new Binding
{
Path = new PropertyPath("ActualWidth"),
Source = textBox
});
StaysOpen = true;
_listBox = new ListBox();
_listBox.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding
{
Path = new PropertyPath(ItemsSourceProperty),
Source = _textBox
});
_listBox.SetBinding(
ItemsControl.DisplayMemberPathProperty,
new Binding
{
Path = new PropertyPath(DisplayMemberPathProperty),
Source = _textBox
});
_listBox.SetBinding(
ItemsControl.ItemTemplateProperty,
new Binding
{
Path = new PropertyPath(ItemTemplateProperty),
Source = _textBox
});
_listBox.AddHandler(Mouse.MouseDownEvent, new MouseButtonEventHandler(_listBox_MouseLeftButtonDown), true);
Child = _listBox;
PlacementTarget = _textBox;
Placement = PlacementMode.Bottom;
MaxHeight = 200;
AllowsTransparency = true;
SetResourceReference(PopupAnimationProperty, SystemParameters.ComboBoxPopupAnimationKey);
Focusable = false;
_textBox.LostFocus += _textBox_LostFocus;
_textBox.TextChanged += _textBox_TextChanged;
_textBox.PreviewKeyDown += _textBox_PreviewKeyDown;
Window window = Window.GetWindow(_textBox);
if (window != null)
window.Deactivated += window_Deactivated;
_textBox.SetValue(AutoCompletePopupPropertyKey, this);
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
RefreshFilter();
}
private void RefreshFilter()
{
var view = CollectionViewSource.GetDefaultView(_listBox.ItemsSource);
if (view != null)
{
view.SortDescriptions.Clear();
string displayMemberPath = GetDisplayMemberPath(_textBox);
if (!string.IsNullOrEmpty(displayMemberPath))
view.SortDescriptions.Add(new System.ComponentModel.SortDescription(displayMemberPath, System.ComponentModel.ListSortDirection.Ascending));
if (string.IsNullOrEmpty(_textBox.Text))
{
view.Filter = null;
}
else
{
view.Filter = FilterItem;
}
}
}
private bool FilterItem(object obj)
{
if (string.IsNullOrEmpty(_currentSearch))
return true;
var comparisonType = GetComparisonType(_textBox);
var matchMode = GetMatchMode(_textBox);
string itemText = GetItemText(obj);
if (itemText != null)
{
switch (matchMode)
{
case StringMatchMode.StartsWith:
return itemText.StartsWith(_currentSearch, comparisonType);
case StringMatchMode.EndsWith:
return itemText.EndsWith(_currentSearch, comparisonType);
case StringMatchMode.Contains:
return itemText.Contains(_currentSearch, comparisonType);
default:
break;
}
}
return false;
}
private string GetItemText(object obj)
{
string path = GetSearchMemberPath(_textBox);
if (string.IsNullOrEmpty(path))
path = GetDisplayMemberPath(_textBox);
if (!string.IsNullOrEmpty(path) && obj != null)
{
obj = PropertyPathHelper.GetValue(obj, path);
}
if (obj != null)
return obj.ToString();
return null;
}
private void _listBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (IsInListBoxItem(e.OriginalSource as DependencyObject))
{
ChooseSelectedText();
IsOpen = false;
}
}
private static bool IsInListBoxItem(DependencyObject o)
{
if (o == null)
return false;
if (o is ListBoxItem)
return true;
DependencyObject parent = VisualTreeHelper.GetParent(o);
return IsInListBoxItem(parent);
}
private void window_Deactivated(object sender, EventArgs e)
{
IsOpen = false;
}
private void _textBox_TextChanged(object sender, RoutedEventArgs e)
{
if (_textChanging)
return;
string searchText = _textBox.Text;
int start = _textBox.SelectionStart;
int length = _textBox.SelectionLength;
if (length != 0)
searchText = _textBox.Text.Substring(0, _textBox.SelectionStart);
IsOpen = true;
if (searchText != _currentSearch)
{
_currentSearch = searchText;
RefreshFilter();
}
if (GetSuggest(_textBox) && !_deleting)
{
object firstSuggestion = _listBox.Items.Cast