wpf - 在 CollectionViewSource 上触发过滤器

标签 wpf xaml mvvm filter collectionviewsource

我正在使用 MVVM 模式开发 WPF 桌面应用程序。

我试图根据 TextBox 中输入的文本从 ListView 中过滤出一些项目。我希望在更改文本时过滤 ListView 项目。

我想知道当过滤器文本发生变化时如何触发过滤器。

ListView 绑定(bind)到 CollectionViewSource,后者又绑定(bind)到我的 ViewModel 上的 ObservableCollection。过滤器文本的 TextBox 绑定(bind)到 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 属性的 setter 调用 FilterList 方法,该方法迭代 ViewModel 中的 ObservableCollection 并设置 boolean FilteredOut每个项目 ViewModel 上的属性。

我知道当过滤器文本更改时 FilteredOut 属性会更新,但列表不会刷新。仅当我通过切换并返回来重新加载 UserControl 时,才会触发 CollectionViewSource 过滤器事件。

我尝试在更新过滤器信息后调用OnPropertyChanged("AllProjects"),但它没有解决我的问题。 (“AllProjects”是我的 ViewModel 上的 ObservableCollection 属性,CollectionViewSource 绑定(bind)到该属性。)

当 FilterText TextBox 的值发生变化时,如何让 CollectionViewSource 重新过滤自身?

非常感谢

最佳答案

不要在 View 中创建CollectionViewSource。相反,在 View 模型中创建一个 ICollectionView 类型的属性并将 ListView.ItemsSource 绑定(bind)到它。

完成此操作后,您可以将逻辑放入 FilterText 属性的 setter 中,每当用户更改它。

您会发现这也简化了排序问题:您可以将排序逻辑构建到 View 模型中,然后公开 View 可以使用的命令。

编辑

这是一个使用 MVVM 动态排序和过滤 Collection View 的非常简单的演示。此演示未实现 FilterText,但是一旦您了解了它的工作原理,那么实现 FilterText 属性和使用该属性的谓词就不会有任何困难它现在使用的硬编码过滤器的。

(另请注意,此处的 View 模型类不实现属性更改通知。这只是为了保持代码简单:由于此演示中没有任何内容实际更改属性值,因此不需要属性更改通知。)

首先为您的项目分类:

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

现在,应用程序的 View 模型。这里发生了三件事:首先,它创建并填充自己的 ICollectionView;其次,它公开了一个 ApplicationCommand (见下文), View 将使用它来执行排序和过滤命令,最后,它实现了一个 Execute 方法来对 View 进行排序或过滤:

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);
        }
    }
}

要触发 View 模型中的 Execute 方法,需要使用 ApplicationCommand 类,该类是 ICommand 的简单实现,用于路由 View 中按钮上的 CommandParameter 传递给 View 模型的 Execute 方法。我以这种方式实现它是因为我不想在应用程序 View 模型中创建一堆 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>

关于wpf - 在 CollectionViewSource 上触发过滤器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6466917/

相关文章:

c# - WPF ObservableCollection 在创建新的 ObservableCollection 时不更新 DataGrid

c# - 多线程 WPF Application : Dispatcher Invoke. 更高效的方法?

c# - WPF 图像控件不处理源

c# - 与 ListView、UserControl、DependencyProperty、ObservableCollection、INotifyPropertyChanged 的​​绑定(bind)错误

c# - Caliburn.Micro WPF : How can I create a new ViewModel that has a dependency?

c# - MVVM Light Messenger 接收方法

c# - WPF 是否有无需第三方库即可进行 2D 图表的首选方法?

c# - 如何使用 WPF MVVM 缩短代码?

c# - 在 WCF 中使用 netsh 添加端口作为没有管理员权限的域管理员

wpf - 类型 'Window'不支持直接内容