在CollectionViewSource上触发filter

我正在使用MVVM模式在WPF桌面应用程序上工作。

我想基于在TextBox键入的文本筛选ListView的一些项目。 我希望ListView项目被过滤,因为我改变了文本。

我想知道如何在filter文本更改时触发filter。

ListView绑定到一个CollectionViewSource ,绑定到ViewModel上的ObservableCollection 。 filter文本的TextBox绑定到ViewModel上的一个string,它应该是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上调用filter方法。

当FilterText的值发生变化时进行筛选 – FilterText属性的setter调用一个FilterList方法,该方法遍历ViewModel中的ObservableCollection ,并在每个ViewModel项上设置一个boolean FilteredOut属性。

我知道FilteredOut属性在filter文本更改时更新,但List不刷新。 CollectionViewSourcefilter事件只有在重新加载UserControl的时候才会被触发。

我已经尝试更新filter信息后调用OnPropertyChanged("AllProjects") ,但它并没有解决我的问题。 (“AllProjects”是我的ViewModel的CollectionViewSource绑定的ObservableCollection属性。)

当FilterText TextBox的值发生变化时,如何让CollectionViewSource重新进行自我刷新?

非常感谢

不要在您的视图中创build一个CollectionViewSource 。 相反,在您的视图模型中创build一个types为ICollectionView的属性并将ListView.ItemsSource绑定到它。

完成此操作后,可以将逻辑放入FilterText属性的setter中,以便在用户更改ICollectionView时调用Refresh()

您会发现这也简化了sorting问题:您可以将sorting逻辑构build到视图模型中,然后显示视图可以使用的命令。

编辑

下面是一个使用MVVMdynamicsorting和过滤集合视图的简单演示。 这个演示没有实现FilterText ,但是一旦你理解了它是如何工作的,你不应该有任何困难实现一个FilterText属性和一个使用该属性的谓词,而不是现在使用的硬编码filter。

(还要注意,这里的视图模型类没有实现属性更改通知,只是为了保持代码简单:因为这个演示中没有任何内容实际上改变属性值,所以不需要属性改变通知。

首先为你的物品上课:

 public class ItemViewModel { public string Name { get; set; } public int Age { get; set; } } 

现在,应用程序的视图模型。 有三件事情在这里:首先,它创build并填充自己的ICollectionView ; 第二,它公开了一个ApplicationCommand (见下文),该视图将用来执行sorting和过滤命令,最后,它实现了一个Execute方法来对视图进行sorting或过滤:

 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的简单实现,它将视图中的button上的CommandParameter路由到视图模型的Execute方法。 我这样实现它,因为我不想在应用程序视图模型中创build一堆RelayCommand属性,而且我希望在一个方法中保留所有的sorting/过滤,以便很容易地看到它是如何完成的。

 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为true,它会根据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中,当然也不在我的Model中。

  • 在我的例子中,我使用了“Filter In”而不是“Filter Out”。 对我来说(在大多数情况下),我正在使用的filter是我想看到的东西,这似乎更符合逻辑。 如果你真的想过滤出来,只是否定Contains子句(即item =>!Item.Text.Contains(…))。

  • 你可能有更集中的方式来做你的视图模型集。 重要的是要记住,当你改变FilterText的时候,你还需要通知你的AllFilteredItems集合。 我在这里做了内联,但是你也可以处理PropertyChanged事件,并在e.PropertyName是FilterText时调用PropertyChanged。

请让我知道,如果你需要任何澄清。

如果我明白你在问什么:

在您的FilterText属性的设置部分,只需调用Refresh()到您的CollectionView

我刚刚发现了一个更加优雅的解决scheme。 而不是创build一个ICollectionView在您的ViewModel(如接受的答案build议),并设置您的绑定

 ItemsSource={Binding Path=YourCollectionViewSourceProperty} 

更好的方法是在ViewModel中创build一个CollectionViewSource属性。 然后如下绑定你的ItemsSource

 ItemsSource={Binding Path=YourCollectionViewSourceProperty.View} 

注意添加.View这样,只要有一个对CollectionViewSource的改变, ItemsSource绑定仍然会被通知,并且你不需要ICollectionView手动调用Refresh()

注意:我无法确定为什么会这样。 如果直接绑定到CollectionViewSource属性,则绑定失败。 但是,如果您在XAML文件的Resources元素中定义CollectionViewSource并直接绑定到资源键,则绑定工作正常。 我唯一能猜到的是,当你在XAML中完全执行它时,它知道你真的想绑定到CollectionViewSource.View的值并且在后台为你进行绑定(多么有帮助!:/)。

 CollectionViewSource.View.Refresh(); 

以这种方式重新评估CollectionViewSource.Filter!