Trigger Filter on CollectionViewSource
我正在使用MVVM模式在WPF桌面应用程序上工作。
我正在尝试根据
我想知道当过滤器文本更改时如何触发过滤器。
1 2 3 4 5 6 7 8 | <CollectionViewSource x:Key="ProjectsCollection" Source="{Binding Path=AllProjects}" Filter="CollectionViewSource_Filter" /> <TextBox Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" /> <ListView DataContext="{StaticResource ProjectsCollection}" ItemsSource="{Binding}" /> |
当FilterText的值更改时完成过滤-FilterText属性的设置器调用FilterList方法,该方法遍历ViewModel中的
我知道当过滤器文本更改时,FilteredOut属性会更新,但列表不会刷新。
我尝试在更新过滤器信息后调用
(" AllProjects"是
当FilterText
非常感谢
不要在视图中创建
完成此操作后,可以将逻辑放入
您会发现这也简化了排序问题:您可以将排序逻辑构建到视图模型中,然后公开视图可以使用的命令。
编辑
这是一个使用MVVM对集合视图进行动态排序和过滤的非常简单的演示。该演示没有实现
(还要注意,这里的视图模型类没有实现属性更改通知。这只是为了保持代码简单:由于此演示中的任何内容实际上都不会更改属性值,因此不需要属性更改通知。)
首先是您的物品的课程:
1 2 3 4 5 | public class ItemViewModel { public string Name { get; set; } public int Age { get; set; } } |
现在,该应用程序的视图模型。这里发生三件事:首先,它创建并填充自己的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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; } } } |
排序有点烂;您需要实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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); } } } |
要触发视图模型中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } |
最后,这是该应用程序的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <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> |
如今,您通常不需要显式触发刷新。
XAML中的一个示例:
1 2 3 4 5 6 7 8 9 | <CollectionViewSource Source="{Binding Items}" Filter="FilterPredicateFunction" IsLiveFilteringRequested="True"> <CollectionViewSource.LiveFilteringProperties> <system:String>FilteredProperty1</system:String> <system:String>FilteredProperty2</system:String> </CollectionViewSource.LiveFilteringProperties> </CollectionViewSource> |
也许您已经在问题中简化了View,但是按照书面要求,您实际上并不需要CollectionViewSource-您可以直接在ViewModel中绑定到过滤列表(mItemsToFilter是要过滤的集合,可能是" AllProjects"您的示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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")); } } } |
您的视图将仅仅是:
1 2 | <TextBox Text="{Binding Path=FilterText,UpdateSourceTrigger=PropertyChanged}" /> <ListView ItemsSource="{Binding AllFilteredItems}" /> |
快速注意事项:
-
这消除了后面代码中的事件
-
它还消除了" FilterOut"属性,该属性是人为的,仅用于GUI的属性,因此实际上破坏了MVVM。除非您计划序列化它,否则我不会在ViewModel中使用它,当然也不会在我的Model中使用它。
-
在我的示例中,我使用"过滤器输入"而不是"过滤器输出"。在大多数情况下,对我来说似乎更合逻辑的是,我正在应用的过滤器是我确实希望看到的东西。如果您确实想过滤掉所有内容,则只需取消Contains子句即可(即item =>!Item.Text.Contains(...))。
-
在ViewModel中,您可能有更集中的方式来进行Sets。要记住的重要一点是,当您更改FilterText时,还需要通知AllFilteredItems集合。我是在这里内联完成的,但是当e.PropertyName为FilterText时,您也可以处理PropertyChanged事件并调用PropertyChanged。
如果您需要任何说明,请告诉我。
1 | CollectionViewSource.View.Refresh(); |
以这种方式重新评估CollectionViewSource.Filter!
我刚刚发现了一个更优雅的解决方案。而不是在ViewModel中创建
1 | ItemsSource={Binding Path=YourCollectionViewSourceProperty} |
更好的方法是在ViewModel中创建一个
1 | ItemsSource={Binding Path=YourCollectionViewSourceProperty.View} |
请注意,添加了.View这样,只要
注意:我无法确定为什么会这样。如果直接绑定到
如果我很了解您的要求:
在