Триггерный фильтр на CollectionViewSource

я работаю над настольным приложением WPF, используя шаблон MVVM.

я пытаюсь отфильтровать некоторые элементы из ListView на основе текста, введенного в TextBox. Я хочу ListView элементы для фильтрации при изменении текста.

я хочу знать, как вызвать фильтр при изменении текста фильтра.

на ListView привязка к CollectionViewSource, который привязывается к ObservableCollection на моей ViewModel. The 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" ссылки на обработчик событий в коде позади, который просто вызывает метод фильтра на ViewModel.

фильтрация выполняется при изменении значения FilterText-сеттер для свойства FilterText вызывает метод FilterList, который выполняет итерацию по ObservableCollection в моей ViewModel и устанавливает boolean свойство FilteredOut для каждого элемента ViewModel.

я знаю FilteredOut свойство обновляется при изменении текста фильтра, но список не обновляется. The CollectionViewSource событие фильтра запускается только тогда, когда я перезагружаю UserControl, переключаясь от него и обратно.

я пытался дозвониться OnPropertyChanged("AllProjects") после обновления информации о фильтре, но это не решило мою проблему. ("AllProjects" - это ObservableCollection свойство на моей ViewModel, к которому CollectionViewSource персонализация.)

как я могу получить CollectionViewSource для повторной фильтрации, когда значение FilterText TextBox изменения?

большое спасибо

6 ответов


не создано!--5--> на ваш взгляд. Вместо этого создайте свойство типа ICollectionView в вашей модели представления и привязки ListView.ItemsSource к нему.

как только вы это сделаете, вы можете поместить логику в FilterText сеттер свойства, который вызывает Refresh() на ICollectionView всякий раз, когда пользователь изменяет его.

вы обнаружите, что это также упрощает проблему сортировки: вы можете построить логику сортировки в модели представления, а затем предоставить команды, которые представление может использовать.

редактировать

вот довольно простая демонстрация динамической сортировки и фильтрации представления коллекции с использованием MVVM. Эта демонстрация не реализует FilterText, но как только вы поймете, как все это работает, у вас не должно быть никаких трудностей с реализацией FilterText свойство и предикат, который использует это свойство вместо жестко закодированного фильтра, который он использует сейчас.

(обратите внимание также, что классы модели представления здесь не реализуют свойство-уведомление об изменении. Это просто, чтобы сохранить код простым: поскольку ничто в этой демонстрации фактически не изменяет значения свойств, ему не нужно уведомление об изменении свойств.)

первый класс для вашего пользования:

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

теперь, модель представления для приложения. Здесь происходит три вещи: во-первых, он создает и населяет свой собственный 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;
        }
    }
}

сортировка типа отстой; вам нужно реализовать 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 что маршруты CommandParameter на кнопках в представлении к модели представления 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;
}

наконец, вот 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 верно, основываясь на полях в его 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, и, конечно, не в моей модели.

  • в моем примере я использую "Filter In", а не"Filter Out". Мне кажется более логичным (в большинстве случаев), что фильтр, который я применяю, - это вещи, которые я do хотите увидеть. Если ты действительно хочешь ... чтобы отфильтровать вещи, просто отрицайте предложение Contains (т. е. item => ! Пункт.Текст.Содержит.(..)).

  • у вас может быть более централизованный способ выполнения Ваших наборов в вашей ViewModel. Важно помнить, что при изменении FilterText вам также необходимо уведомить свою коллекцию AllFilteredItems. Я сделал это здесь, но вы также можете обработать событие PropertyChanged и вызвать PropertyChanged, когда e.PropertyName FilterText.

пожалуйста, дайте мне знать если вам нужны любые разъяснения.


CollectionViewSource.View.Refresh();

CollectionViewSource.Фильтр переоценен таким образом!


Если бы я хорошо понял, о чем вы спрашиваете:

в установленный частью FilterText свойство просто вызов Refresh() на CollectionView.


я только что обнаружил гораздо более элегантное решение этой проблемы. вместо создания ICollectionView в вашей ViewModel (как предполагает принятый ответ) и установка привязки к

ItemsSource={Binding Path=YourCollectionViewSourceProperty}

лучший способ-создать CollectionViewSource свойства в ViewModel. Тогда свяжи свой ItemsSource следующим образом

ItemsSource={Binding Path=YourCollectionViewSourceProperty.View}    

обратите внимание на добавление .Вид сюда ItemsSource привязка по-прежнему уведомляется всякий раз, когда происходит изменение CollectionViewSource и вам никогда не придется вручную вызвать Refresh() на ICollectionView

примечание: Я не могу определить, почему это происходит. Если вы привязываетесь непосредственно к CollectionViewSource свойство сбой привязки. Однако, если вы определяете CollectionViewSource в своем Resources элемент файла XAML и привязка непосредственно к ключу ресурса, привязка работает нормально. Единственное, что я могу предположить, что когда вы делаете это полностью в XAML, он знает, что вы действительно хотите привязаться к CollectionViewSource.Значение вид и привязывает его к вам acourdingly за кадром (как полезно! :/) .