Ich habe dies erst vor ein paar Tagen mit einer modifizierten Version des Codes von dieser Website getan: Ehre, wem Ehre gebührt
Mein vollständiger Code ist unten aufgeführt:
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());
}
}
}
Und die WPF sollte etwa so sein:
<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>
Hier sind ein paar Dinge zu beachten. Sie werden feststellen, dass die FilterItem-Implementierung einen ToString() auf das Objekt anwendet. Das bedeutet, dass die Eigenschaft Ihres Objekts, die Sie anzeigen möchten, in Ihrer object.ToString()-Implementierung zurückgegeben werden sollte (oder bereits ein String ist). (oder bereits eine Zeichenkette sein) Mit anderen Worten etwa so:
public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
public override string ToString()
{
return Name;
}
}
Wenn dies nicht für Ihre Bedürfnisse funktioniert, nehme ich an, Sie könnten den Wert von DisplayMemberPath erhalten und Reflexion verwenden, um die Eigenschaft zu verwenden, aber das wäre langsamer, so dass ich nicht empfehlen würde, das zu tun, es sei denn notwendig.
Auch diese Implementierung hält den Benutzer NICHT davon ab, in den TextBox-Teil der ComboBox einzugeben, was immer er möchte. Wenn sie dort etwas Dummes eingeben, wird das SelectedItem auf NULL zurückgesetzt, also seien Sie darauf vorbereitet, das in Ihrem Code zu behandeln.
Auch wenn Sie viele Elemente haben, würde ich sehr empfehlen, die VirtualizingStackPanel wie mein Beispiel oben, wie es macht einen Unterschied in der Ladezeit