在CollectionViewSource上触发过滤器 [英] Trigger Filter on CollectionViewSource

查看:209
本文介绍了在CollectionViewSource上触发过滤器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我试图从 ListView中过滤一些项目

code>基于在 TextBox 中键入的文本。我希望在更改文本时过滤 ListView 项。



我想知道在过滤器文本改变时触发过滤器。
$ b $ < ListView
绑定到 CollectionViewSource ,它绑定到我的ViewModel上的 ObservableCollection 。过滤器文本的 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 links to在后面的代码中的事件处理程序,它只是在ViewModel上调用一个过滤器方法。



过滤是在FilterText的值发生变化时完成的 - FilterText属性调用的setter一个FilterList方法遍历ViewModel中的 ObservableCollection ,并在每个ViewModel项上设置一个布尔值 FilteredOut属性。 p>

我知道FilteredOut属性在过滤器文本更改时更新,但List不刷新。

我只是在重新加载UserControl的时候触发 CollectionViewSource 过滤器事件。在更新过滤器信息后,我尝试调用 OnPropertyChanged(AllProjects),但是没有解决我的问题。
(AllProjects是我的ViewModel上 CollectionViewSource 绑定的 ObservableCollection 属性。 p>

当FilterText TextBox 时,如何获取 CollectionViewSource code> change?



非常感谢

解决方案

在你的视图中创建一个 CollectionViewSource 。相反,在你的视图模型中创建一个类型为 ICollectionView 的属性,并将 ListView.ItemsSource 绑定到它。



完成此操作后,可以将逻辑放入 FilterText 属性的setter中,该setter调用 Refresh )>

您会发现这也简化了排序问题:您可以在视图模型中构建排序逻辑,然后展示视图可以使用的命令。

编辑



下面是一个使用MVVM动态排序和过滤集合视图的简单演示。这个演示没有实现 FilterText ,但是一旦你理解了它是如何工作的,你不应该有任何困难实现一个 FilterText 属性以及使用该属性的谓词,而不是现在使用的硬编码过滤器。 (注意这里的视图模型类没有实现属性更改通知,这只是为了保持代码简单:因为这个演示中没有任何东西实际上改变了属性值)

首先为你的物品添加一个类:



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

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

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;组; }
$ b $ public void ExecuteCommand(string command)
{
ListCollectionView list =(ListCollectionView)ItemsView;
switch(command)
{
caseSortByName:
list.CustomSort = new ItemSorter(Name);
return;
caseSortByAge:
list.CustomSort = new ItemSorter(Age);
return;
caseApplyFilter:
list.Filter = new Predicate< object>(x =>
((ItemViewModel)x).Age> 21);
return;
caseRemoveFilter:
list.Filter = null;
return;
默认值:
return;





排序类型的吸引;你需要实现一个 IComparer


$ b

public class ItemSorter:IComparer
{
private string PropertyName {get;组; }
$ b $ public ItemSorter(string propertyName)
{
PropertyName = propertyName;
}
public int Compare(object x,object y)
{
ItemViewModel ix =(ItemViewModel)x;
ItemViewModel iy =(ItemViewModel)y;

switch(PropertyName)
{
caseName:
return string.Compare(ix.Name,iy.Name);
caseAge:
if(ix.Age> iy.Age)return 1;
if(iy.Age> ix.Age)返回-1;
返回0;
默认值:
抛出新的InvalidOperationException(无法排序+
PropertyName);


$ b code
$ b

触发 Execute 方法,它使用一个 ApplicationCommand 类,它是一个简单的实现 ICommand ,它将视图中的按钮上的 CommandParameter 路由到视图模型的 Execute 方法。我这样实现它,因为我不想在应用程序视图模型中创建一堆 RelayCommand 属性,并且我希望在一个方法中保留所有的排序/过滤所以很容易看到它是如何完成的。

public class ApplicationCommand:ICommand
{
private ApplicationViewModel _ApplicationViewModel;

public ApplicationCommand(ApplicationViewModel avm)
{
_ApplicationViewModel = avm;

$ b $ public void Execute(object parameter)
{
_ApplicationViewModel.ExecuteCommand(parameter.ToString());


public bool CanExecute(object parameter)
{
return true;
}

公共事件EventHandler CanExecuteChanged;





最后,这里是 MainWindow 为应用程序:
$ b

 < 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 =MainWindowHeight =350Width =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>按名称排序< / Button>
< Button Command ={Binding ApplicationCommand}
CommandParameter =SortByAge>按年龄排序< / Button>
< Button Command ={Binding ApplicationCommand}
CommandParameter =ApplyFilter>应用过滤器< / Button>
< Button Command ={Binding ApplicationCommand}
CommandParameter =RemoveFilter>删除过滤器< / Button>
< / StackPanel>
< / DockPanel>
< / Window>


I am working on a WPF desktop application using the MVVM pattern.

I am trying to filter some items out of a ListView based on the text typed in a TextBox. I want the ListView items to be filtered as I change the text.

I want to know how to trigger the filter when the filter text changes.

The ListView binds to a CollectionViewSource, which binds to the ObservableCollection on my ViewModel. The TextBox for the filter text binds to a string on the ViewModel, with UpdateSourceTrigger=PropertyChanged, as it should be.

<CollectionViewSource x:Key="ProjectsCollection"
                      Source="{Binding Path=AllProjects}"
                      Filter="CollectionViewSource_Filter" />

<TextBox Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />

<ListView DataContext="{StaticResource ProjectsCollection}"
          ItemsSource="{Binding}" />

The Filter="CollectionViewSource_Filter" links to an event handler in the code behind, which simply calls a filter method on the ViewModel.

Filtering is done when the value of FilterText changes - the setter for the FilterText property calls a FilterList method that iterates over the ObservableCollection in my ViewModel and sets a boolean FilteredOut property on each item ViewModel.

I know the FilteredOut property is updated when the filter text changes, but the List does not refresh. The CollectionViewSource filter event is only fired when I reload the UserControl by switching away from it and back again.

I've tried calling OnPropertyChanged("AllProjects") after updating the filter info, but it did not solve my problem. ("AllProjects" is the ObservableCollection property on my ViewModel to which the CollectionViewSource binds.)

How can I get the CollectionViewSource to refilter itself when the value of the FilterText TextBox changes?

Many thanks

解决方案

Don't create a CollectionViewSource in your view. Instead, create a property of type ICollectionView in your view model and bind ListView.ItemsSource to it.

Once you've done this, you can put logic in the FilterText property's setter that calls Refresh() on the ICollectionView whenever the user changes it.

You'll find that this also simplifies the problem of sorting: you can build the sorting logic into the view model and then expose commands that the view can use.

EDIT

Here's a pretty straightforward demo of dynamic sorting and filtering of a collection view using MVVM. This demo doesn't implement FilterText, but once you understand how it all works, you shouldn't have any difficulty implementing a FilterText property and a predicate that uses that property instead of the hard-coded filter that it's using now.

(Note also that the view model classes here don't implement property-change notification. That's just to keep the code simple: as nothing in this demo actually changes property values, it doesn't need property-change notification.)

First a class for your items:

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

Now, a view model for the application. There are three things going on here: first, it creates and populates its own ICollectionView; second, it exposes an ApplicationCommand (see below) that the view will use to execute sorting and filtering commands, and finally, it implements an Execute method that sorts or filters the 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;
        }
    }
}

Sorting kind of sucks; you need to implement an 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);
        }
    }
}

To trigger the Execute method in the view model, this uses an ApplicationCommand class, which is a simple implementation of ICommand that routes the CommandParameter on buttons in the view to the view model's Execute method. I implemented it this way because I didn't want to create a bunch of RelayCommand properties in the application view model, and I wanted to keep all the sorting/filtering in one method so that it was easy to see how it's done.

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

Finally, here's the MainWindow for the application:

<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上触发过滤器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆