Wie filtere ich Combobox-Elemente basierend auf dem Eingabetext?
Ich möchte ein Kombinationsfeld anzeigen, dessen Elemente vom Ansichtsmodell bereitgestellt werden. Das Kombinationsfeld sollte bearbeitbar sein. Basierend auf dem aktuell vom Benutzer eingegebenen Text sollten die Kombinationsfeldelemente gefiltert werden.
Ich versuche, die folgende Lösung anzuwenden, auf die in verschiedenen Ressourcen zum Thema hingewiesen wird (z. B. diese Frage , diese Frage , dieser Artikel , diese Frage , dieser Blogpost , dieses Tutorial usw.):
- Mein Ansichtsmodell bietet eine Sammlungsansicht um die Elemente.
- Ich habe die
Text
Eigenschaft des Kombinationsfelds in beide Richtungen an eineCustomText
Eigenschaft in meinem Ansichtsmodell gebunden . - Das
Filter
Prädikat in der Sammlungsansicht wird festgelegt, um Elemente basierend darauf zu überprüfen, ob ihr Anzeigename das enthältCustomText
. - Wenn
CustomText
geändert wird, wird dieRefresh
Methode in der Elementauflistungsansicht aufgerufen.
Ich würde erwarten, dass dies die Liste der Elemente in der Dropdown-Liste des Kombinationsfelds aktualisiert, wenn ich den Text ändere. Leider bleibt die Liste gleich.
Wenn ich einen Haltepunkt in mein Filter
Prädikat setze, wird er getroffen, aber irgendwie nicht immer für jedes Element.
Hier ist ein minimales Beispiel:
Xaml für das Fenster:
<Window x:Class="ComboBoxFilterTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ComboBoxFilterTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ComboBox
VerticalAlignment="Center"
ItemsSource="{Binding Items}"
DisplayMemberPath="Name"
IsEditable="True"
Text="{Binding CustomText}"
IsTextSearchEnabled="False"/>
</Grid>
</Window>
Der Code-Behind für das Fenster:
using System.Windows;
namespace ComboBoxFilterTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}
Und das Ansichtsmodell (hier mit der Item
Datenklasse, die sich normalerweise woanders befindet):
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
namespace ComboBoxFilterTest
{
public class MainViewModel : INotifyPropertyChanged
{
private sealed class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
public MainViewModel()
{
Items = new CollectionView(items)
{
Filter = item =>
{
if (string.IsNullOrEmpty(customText))
{
return true;
}
if (item is Item typedItem)
{
return typedItem.Name.ToLowerInvariant().Contains(customText.ToLowerInvariant());
}
return false;
}
};
}
private readonly ObservableCollection<Item> items = new ObservableCollection<Item>
{
new Item{ Id = 1, Name = "ABC" },
new Item{ Id = 2, Name = "ABCD" },
new Item{ Id = 3, Name = "XYZ" }
};
public ICollectionView Items { get; }
private string customText = "";
public event PropertyChangedEventHandler PropertyChanged;
public string CustomText
{
get => customText;
set
{
if (customText != value)
{
customText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CustomText)));
Items.Refresh();
}
}
}
}
}
Grundsätzlich denke ich, dass ich das Gleiche tue wie in einer anderen Frage beschrieben , aber anscheinend ist etwas immer noch anders, da es in meinem Fall nicht funktioniert.
Beachten Sie, dass ein kleiner Unterschied darin besteht, dass ich nicht verwende CollectionViewSource.GetDefaultView
, da ich mehrere unterschiedlich gefilterte Ansichten für dieselbe Sammlung haben möchte, anstatt die Standardansicht zu erhalten.
Beachten Sie, dass ich zur Umgehung dieses Problems natürlich einfach die entsprechend gefilterte Aufzählung von Elementen selbst zurückgeben und bei jeder Änderung des Filters ein Ereignis mit geänderter Eigenschaft für eine solche aufzählbare Eigenschaft auslösen könnte. Ich verstehe jedoch, dass das Verlassen auf Sammlungsansichten der richtige WPF-Weg ist, daher würde ich es vorziehen, es "richtig" zu machen.
Antworten
Das empfohlene Muster, um Probleme wie das auftretende zu vermeiden, ist die Verwendung CollectionViewSource
als Bindungsquelle.
Wie auch in den Dokumenten erwähnt, sollten Sie niemals Instanzen von CollectionView
manuell erstellen . Sie müssen einen speziellen Untertyp verwenden, der dem tatsächlichen Typ der Quellensammlung entspricht:
"Sie sollten keine Objekte dieser Klasse [
CollectionView
] in Ihrem Code erstellen. Um eine Sammlungsansicht für eine Sammlung zu erstellen, die nur IEnumerable implementiert, erstellen Sie ein CollectionViewSource-Objekt, fügen Sie Ihre Sammlung der Source-Eigenschaft hinzu und rufen Sie die Sammlungsansicht über die View-Eigenschaft ab . " Microsoft Docs: CollectionView
CollectionViewSource
führt intern die Typprüfung für Sie durch und erstellt eine ordnungsgemäß initialisierte ICollectionView
Implementierung, die für die eigentliche Quellensammlung geeignet ist. CollectionViewSource
Unabhängig davon, ob in XAML oder C # erstellt, wird empfohlen, eine Instanz von zu erhalten ICollectionView
, wenn die Standardansicht nicht ausreicht:
public ICollectionView Items { get; }
public CollectionViewSource ItemsViewSource { get; }
public ctor()
{
ObservableCollection<object> items = CreateObservableItems();
this.ItemsViewSource = new CollectionViewSource() {Source = items};
this.Items = this.ItemsViewSource.View;
}
Ich glaube, ich habe eine Lösung gefunden: Wie in einer Antwort zu einem verwandten Thema vorgeschlagen , habe ich ListCollectionView
stattdessen verwendet CollectionView
.
Aus irgendeinem Grund funktioniert es mit, ListCollectionView
während es nicht mit funktioniert CollectionView
, obwohl letzteres keinen Hinweis darauf gibt, dass dies nicht der Fall sein sollte (z . B. CollectionView.CanFilter
Rückgabe true
).
Ich werde diese Antwort vorerst selbst akzeptieren. Wenn jedoch jemand eine Antwort geben kann, die tatsächlich eine Erklärung für dieses Verhalten liefert, werde ich stattdessen gerne eine solche Antwort akzeptieren.