Триггерный фильтр на CollectionViewSource
я работаю над настольным приложением WPF, используя шаблон MVVM.
я пытаюсь отфильтровать некоторые элементы из ListView
на основе текста, введенного в TextBox
. Я хочу ListView
элементы для фильтрации при изменении текста.
я хочу знать, как вызвать фильтр при изменении текста фильтра.
на ListView
привязка к CollectionViewSource
, который привязывается к ObservableCollection
на моей ViewModel. The TextBox
для привязки текста фильтра к строка на ViewModel, с UpdateSourceTrigger=PropertyChanged
, как следует.
<CollectionViewSource x:Key="ProjectsCollection"
Source="{Binding Path=AllProjects}"
Filter="CollectionViewSource_Filter" />
<TextBox Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />
<ListView DataContext="{StaticResource ProjectsCollection}"
ItemsSource="{Binding}" />
на Filter="CollectionViewSource_Filter"
ссылки на обработчик событий в коде позади, который просто вызывает метод фильтра на ViewModel.
фильтрация выполняется при изменении значения FilterText-сеттер для свойства FilterText вызывает метод FilterList, который выполняет итерацию по ObservableCollection
в моей ViewModel и устанавливает boolean
свойство FilteredOut для каждого элемента ViewModel.
я знаю FilteredOut свойство обновляется при изменении текста фильтра, но список не обновляется. The CollectionViewSource
событие фильтра запускается только тогда, когда я перезагружаю UserControl, переключаясь от него и обратно.
я пытался дозвониться OnPropertyChanged("AllProjects")
после обновления информации о фильтре, но это не решило мою проблему.
("AllProjects" - это ObservableCollection
свойство на моей ViewModel, к которому CollectionViewSource
персонализация.)
как я могу получить CollectionViewSource
для повторной фильтрации, когда значение FilterText TextBox
изменения?
большое спасибо
6 ответов
не создано!--5--> на ваш взгляд. Вместо этого создайте свойство типа ICollectionView
в вашей модели представления и привязки ListView.ItemsSource
к нему.
как только вы это сделаете, вы можете поместить логику в FilterText
сеттер свойства, который вызывает Refresh()
на ICollectionView
всякий раз, когда пользователь изменяет его.
вы обнаружите, что это также упрощает проблему сортировки: вы можете построить логику сортировки в модели представления, а затем предоставить команды, которые представление может использовать.
редактировать
вот довольно простая демонстрация динамической сортировки и фильтрации представления коллекции с использованием MVVM. Эта демонстрация не реализует FilterText
, но как только вы поймете, как все это работает, у вас не должно быть никаких трудностей с реализацией FilterText
свойство и предикат, который использует это свойство вместо жестко закодированного фильтра, который он использует сейчас.
(обратите внимание также, что классы модели представления здесь не реализуют свойство-уведомление об изменении. Это просто, чтобы сохранить код простым: поскольку ничто в этой демонстрации фактически не изменяет значения свойств, ему не нужно уведомление об изменении свойств.)
первый класс для вашего пользования:
public class ItemViewModel
{
public string Name { get; set; }
public int Age { get; set; }
}
теперь, модель представления для приложения. Здесь происходит три вещи: во-первых, он создает и населяет свой собственный ICollectionView
; во-вторых, она предоставляет ApplicationCommand
(см. ниже), которое представление будет использовать для выполнения команд сортировки и фильтрации, и наконец, он реализует Execute
метод, который сортирует и фильтрует вид:
public class ApplicationViewModel
{
public ApplicationViewModel()
{
Items.Add(new ItemViewModel { Name = "John", Age = 18} );
Items.Add(new ItemViewModel { Name = "Mary", Age = 30} );
Items.Add(new ItemViewModel { Name = "Richard", Age = 28 } );
Items.Add(new ItemViewModel { Name = "Elizabeth", Age = 45 });
Items.Add(new ItemViewModel { Name = "Patrick", Age = 6 });
Items.Add(new ItemViewModel { Name = "Philip", Age = 11 });
ItemsView = CollectionViewSource.GetDefaultView(Items);
}
public ApplicationCommand ApplicationCommand
{
get { return new ApplicationCommand(this); }
}
private ObservableCollection<ItemViewModel> Items =
new ObservableCollection<ItemViewModel>();
public ICollectionView ItemsView { get; set; }
public void ExecuteCommand(string command)
{
ListCollectionView list = (ListCollectionView) ItemsView;
switch (command)
{
case "SortByName":
list.CustomSort = new ItemSorter("Name") ;
return;
case "SortByAge":
list.CustomSort = new ItemSorter("Age");
return;
case "ApplyFilter":
list.Filter = new Predicate<object>(x =>
((ItemViewModel)x).Age > 21);
return;
case "RemoveFilter":
list.Filter = null;
return;
default:
return;
}
}
}
сортировка типа отстой; вам нужно реализовать IComparer
:
public class ItemSorter : IComparer
{
private string PropertyName { get; set; }
public ItemSorter(string propertyName)
{
PropertyName = propertyName;
}
public int Compare(object x, object y)
{
ItemViewModel ix = (ItemViewModel) x;
ItemViewModel iy = (ItemViewModel) y;
switch(PropertyName)
{
case "Name":
return string.Compare(ix.Name, iy.Name);
case "Age":
if (ix.Age > iy.Age) return 1;
if (iy.Age > ix.Age) return -1;
return 0;
default:
throw new InvalidOperationException("Cannot sort by " +
PropertyName);
}
}
}
, чтобы вызвать Execute
метод в модели представления, это использует ApplicationCommand
класс, который является простой реализацией ICommand
что маршруты CommandParameter
на кнопках в представлении к модели представления Execute
метод. Я реализовал его таким образом, потому что я не хотел создавать кучу RelayCommand
свойства в модели представления приложения, и я хотел сохранить всю сортировку / фильтрацию в одном методе, чтобы было легко увидеть, как это делается.
public class ApplicationCommand : ICommand
{
private ApplicationViewModel _ApplicationViewModel;
public ApplicationCommand(ApplicationViewModel avm)
{
_ApplicationViewModel = avm;
}
public void Execute(object parameter)
{
_ApplicationViewModel.ExecuteCommand(parameter.ToString());
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
наконец, вот MainWindow
приложения:
<Window x:Class="CollectionViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CollectionViewDemo="clr-namespace:CollectionViewDemo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<CollectionViewDemo:ApplicationViewModel />
</Window.DataContext>
<DockPanel>
<ListView ItemsSource="{Binding ItemsView}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}"
Header="Name" />
<GridViewColumn DisplayMemberBinding="{Binding Age}"
Header="Age"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel DockPanel.Dock="Right">
<Button Command="{Binding ApplicationCommand}"
CommandParameter="SortByName">Sort by name</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="SortByAge">Sort by age</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="ApplyFilter">Apply filter</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="RemoveFilter">Remove filter</Button>
</StackPanel>
</DockPanel>
</Window>
В настоящее время вам часто не нужно явно запускать обновления. CollectionViewSource
осуществляет ICollectionViewLiveShaping
который обновляется автоматически, если IsLiveFilteringRequested
верно, основываясь на полях в его LiveFilteringProperties
коллекция.
пример в XAML:
<CollectionViewSource
Source="{Binding Items}"
Filter="FilterPredicateFunction"
IsLiveFilteringRequested="True">
<CollectionViewSource.LiveFilteringProperties>
<system:String>FilteredProperty1</system:String>
<system:String>FilteredProperty2</system:String>
</CollectionViewSource.LiveFilteringProperties>
</CollectionViewSource>
возможно, вы упростили свое представление в своем вопросе, но, как написано, вам действительно не нужен CollectionViewSource - вы можете привязаться к отфильтрованному списку непосредственно в ViewModel (mItemsToFilter-это коллекция, которая фильтруется, вероятно, "AllProjects"в вашем примере):
public ReadOnlyObservableCollection<ItemsToFilter> AllFilteredItems
{
get
{
if (String.IsNullOrEmpty(mFilterText))
return new ReadOnlyObservableCollection<ItemsToFilter>(mItemsToFilter);
var filtered = mItemsToFilter.Where(item => item.Text.Contains(mFilterText));
return new ReadOnlyObservableCollection<ItemsToFilter>(
new ObservableCollection<ItemsToFilter>(filtered));
}
}
public string FilterText
{
get { return mFilterText; }
set
{
mFilterText = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("FilterText"));
PropertyChanged(this, new PropertyChangedEventArgs("AllFilteredItems"));
}
}
}
ваш взгляд будет тогда просто:
<TextBox Text="{Binding Path=FilterText,UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding AllFilteredItems}" />
некоторые быстрые заметки:
Это исключает событие в коде за
Он также устраняет свойство "FilterOut", которое является искусственным свойством только для GUI и, таким образом, действительно нарушает MVVM. Если вы не планируете сериализовать это, я бы не хотел, чтобы это было в моей ViewModel, и, конечно, не в моей модели.
в моем примере я использую "Filter In", а не"Filter Out". Мне кажется более логичным (в большинстве случаев), что фильтр, который я применяю, - это вещи, которые я do хотите увидеть. Если ты действительно хочешь ... чтобы отфильтровать вещи, просто отрицайте предложение Contains (т. е. item => ! Пункт.Текст.Содержит.(..)).
у вас может быть более централизованный способ выполнения Ваших наборов в вашей ViewModel. Важно помнить, что при изменении FilterText вам также необходимо уведомить свою коллекцию AllFilteredItems. Я сделал это здесь, но вы также можете обработать событие PropertyChanged и вызвать PropertyChanged, когда e.PropertyName FilterText.
пожалуйста, дайте мне знать если вам нужны любые разъяснения.
Если бы я хорошо понял, о чем вы спрашиваете:
в установленный частью FilterText
свойство просто вызов Refresh()
на CollectionView
.
я только что обнаружил гораздо более элегантное решение этой проблемы. вместо создания ICollectionView
в вашей ViewModel (как предполагает принятый ответ) и установка привязки к
ItemsSource={Binding Path=YourCollectionViewSourceProperty}
лучший способ-создать CollectionViewSource
свойства в ViewModel. Тогда свяжи свой ItemsSource
следующим образом
ItemsSource={Binding Path=YourCollectionViewSourceProperty.View}
обратите внимание на добавление .Вид сюда ItemsSource
привязка по-прежнему уведомляется всякий раз, когда происходит изменение CollectionViewSource
и вам никогда не придется вручную вызвать Refresh()
на ICollectionView
примечание: Я не могу определить, почему это происходит. Если вы привязываетесь непосредственно к CollectionViewSource
свойство сбой привязки. Однако, если вы определяете CollectionViewSource
в своем Resources
элемент файла XAML и привязка непосредственно к ключу ресурса, привязка работает нормально. Единственное, что я могу предположить, что когда вы делаете это полностью в XAML, он знает, что вы действительно хотите привязаться к CollectionViewSource.Значение вид и привязывает его к вам acourdingly за кадром (как полезно! :/) .