Je viens de faire ce quelques jours à l'aide il y a une version modifiée du code de ce site: Credit where credit is due
Mon code complet ci-dessous:
using System.Collections;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace MyControls
{
public class FilteredComboBox : ComboBox
{
private string oldFilter = string.Empty;
private string currentFilter = string.Empty;
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterItem;
}
if (oldValue != null)
{
var view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null) view.Filter -= FilterItem;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
case Key.Enter:
IsDropDownOpen = false;
break;
case Key.Escape:
IsDropDownOpen = false;
SelectedIndex = -1;
Text = currentFilter;
break;
default:
if (e.Key == Key.Down) IsDropDownOpen = true;
base.OnPreviewKeyDown(e);
break;
}
// Cache text
oldFilter = Text;
}
protected override void OnKeyUp(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
break;
case Key.Tab:
case Key.Enter:
ClearFilter();
break;
default:
if (Text != oldFilter)
{
RefreshFilter();
IsDropDownOpen = true;
EditableTextBox.SelectionStart = int.MaxValue;
}
base.OnKeyUp(e);
currentFilter = Text;
break;
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
ClearFilter();
var temp = SelectedIndex;
SelectedIndex = -1;
Text = string.Empty;
SelectedIndex = temp;
base.OnPreviewLostKeyboardFocus(e);
}
private void RefreshFilter()
{
if (ItemsSource == null) return;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Refresh();
}
private void ClearFilter()
{
currentFilter = string.Empty;
RefreshFilter();
}
private bool FilterItem(object value)
{
if (value == null) return false;
if (Text.Length == 0) return true;
return value.ToString().ToLower().Contains(Text.ToLower());
}
}
}
Et le WPF devrait être quelque chose comme ceci:
<MyControls:FilteredComboBox ItemsSource="{Binding MyItemsSource}"
SelectedItem="{Binding MySelectedItem}"
DisplayMemberPath="Name"
IsEditable="True"
IsTextSearchEnabled="False"
StaysOpenOnEdit="True">
<MyControls:FilteredComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel VirtualizationMode="Recycling" />
</ItemsPanelTemplate>
</MyControls:FilteredComboBox.ItemsPanel>
</MyControls:FilteredComboBox>
Quelques points à noter ici. Vous remarquerez que l'implémentation de FilterItem effectue un ToString() sur l'objet. Cela signifie que la propriété de l'objet que vous souhaitez afficher doit être renvoyée dans votre implémentation object.ToString(). (Ou être une chaîne déjà) En d'autres termes quelque chose comme ceci:
public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
public override string ToString()
{
return Name;
}
}
Si cela ne fonctionne pas à vos besoins, je suppose que vous pourriez obtenir la valeur de DisplayMemberPath et de réflexion d'utilisation pour obtenir la propriété de l'utiliser, mais ce serait plus lent, donc je ne recommanderais pas de le faire à moins que nécessaire.
Cette implémentation n'empêche PAS l'utilisateur de taper ce qu'il veut dans la partie TextBox du ComboBox. S'ils tapent quelque chose de stupide, le SelectedItem reviendra à NULL, alors préparez-vous à gérer cela dans votre code.
Aussi, si vous avez beaucoup d'articles que je recommande fortement d'utiliser VirtualizingStackPanel comme mon exemple ci-dessus comme il fait une différence dans le temps de chargement
Cela semble assez cool, peut-être que je ne l'implémente pas encore correctement. J'ai besoin que toute la liste soit disponible s'ils ne veulent pas utiliser la fonctionnalité de recherche. Comment sélectionner une liste complète? Aussi, bien que je puisse lier l'ItemsSource à votre contrôle (en utilisant la liaison dans mon exemple ci-dessus), je ne peux pas réellement sélectionner quelque chose dans la liste ... la zone de texte est toujours vide. Enfin, ce contrôle est central à un groupe de contrôles. J'ai besoin de SelectedItem dans les contrôles adjacents, comme dans: SelectedValue = "{Binding SelectedItem.SomeOtherProperty, ElementName = DiagnosisComboBox ...}" /> – Bob
Vérifiez à nouveau l'article, il fournit tout ce dont vous avez besoin. Si vous souhaitez rendre toute la liste disponible, effacez la propriété 'MaxCompletions' et demandez à votre prédicat de filtre de renvoyer true. Pour activer réellement la sélection d'un élément de la liste, vous devez définir la propriété 'Binding' sur l'une des propriétés de vos objets de données dans la liste. –