CollectionViewSource上的触发器过滤器

Pie*_*ler 44 wpf xaml filter mvvm collectionviewsource

我正在使用MVVM模式处理WPF桌面应用程序.

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

我想知道在过滤器文本更改时如何触发过滤器.

ListView绑定到CollectionViewSource,它绑定到ObservableCollection我的视图模型.该TextBox用于过滤文字结合在视图模型的字符串,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}" />
Run Code Online (Sandbox Code Playgroud)

Filter="CollectionViewSource_Filter"指向后面代码中的事件处理程序的链接,它只是在ViewModel上调用过滤器方法.

当FilterText的值发生更改时进行过滤 - FilterText属性的setter调用FilterList方法,该方法迭代ObservableCollection我的ViewModel并boolean在每个项目ViewModel上设置FilteredOut属性.

我知道过滤器文本更改时FilteredOut属性会更新,但List不会刷新.该CollectionViewSource当我切换远离它,并再次重新加载用户控件过滤事件仅触发.

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

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

非常感谢

Rob*_*ney 70

不要CollectionViewSource在视图中创建.而是ICollectionView在视图模型中创建一个类型的属性并绑定ListView.ItemsSource到它.

一旦你做到了这一点,你可以把逻辑在FilterText属性的setter方法调用Refresh()ICollectionView,每当用户改变它.

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

编辑

这是使用MVVM动态排序和过滤集合视图的非常简单的演示.这个演示没有实现FilterText,但是一旦你理解它是如何工作的,你应该没有任何困难实现FilterText使用该属性的属性和谓词而不是它现在使用的硬编码过滤器.

(另请注意,此处的视图模型类不实现属性更改通知.这只是为了保持代码简单:因为此演示中的任何内容实际上都不会更改属性值,所以它不需要属性更改通知.)

首先是您的项目的课程:

public class ItemViewModel
{
    public string Name { get; set; }
    public int Age { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

现在,该应用程序的视图模型.这里有三件事:首先,它创造和填充自己的东西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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

排序很糟糕; 你需要实现一个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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Execute在视图模型中触发该方法,这将使用一个ApplicationCommand类,该类是一个简单的实现,ICommand它将CommandParameter视图中的on按钮路由到视图模型的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;
}
Run Code Online (Sandbox Code Playgroud)

最后,这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>
Run Code Online (Sandbox Code Playgroud)


Dre*_*kes 24

如今,您通常不需要明确地触发刷新.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>
Run Code Online (Sandbox Code Playgroud)

  • 我想说这是在带有.net 4.5的wpf中添加的 (7认同)
  • 这似乎有些短视。没有自定义绑定?如果您在视图中有项目集合,则可能会更改父视图模型上的某些值(例如,过滤器文本或布尔过滤器标志)。不只是要过滤的集合中项目的属性。 (2认同)
  • 恕我直言,这是要走的路。接受的答案将需要依赖于视图模型中的表示框架,并且我试图使其远离我的视图模型,以便我可以在以后更轻松地交换 UI 层(例如,换成 Uno 框架) 。 (2认同)

Won*_*ane 6

也许你已经在你的问题中简化了你的View,但是如你所写的,你并不真正需要一个CollectionViewSource - 你可以直接在ViewModel中绑定到一个过滤的列表(mItemsToFilter是被过滤的集合,可能是"AllProjects"in你的例子):

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"));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您的视图将只是:

<TextBox Text="{Binding Path=FilterText,UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding AllFilteredItems}" />
Run Code Online (Sandbox Code Playgroud)

一些快速说明:

  • 这消除了后面代码中的事件

  • 它还消除了"FilterOut"属性,这是一个人为的,仅限GUI的属性,因此真正打破了MVVM.除非你打算序列化这个,否则我不希望它在我的ViewModel中,当然也不在我的模型中.

  • 在我的例子中,我使用"Filter In"而不是"Filter Out".这似乎更符合逻辑,我(在大多数情况下),我将滤波器件事情我希望看到的.如果你真的想要过滤掉东西,只需取消Contains子句(即item =>!Item.Text.Contains(...)).

  • 您可以在ViewModel中使用更集中的方式来执行集合.要记住的重要一点是,当您更改FilterText时,还需要通知AllFilteredItems集合.我在这里内联它,但你也可以处理PropertyChanged事件并在e.PropertyName是FilterText时调用PropertyChanged.

如果您需要任何说明,请告诉我.


tux*_*y42 5

CollectionViewSource.View.Refresh();
Run Code Online (Sandbox Code Playgroud)

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