过滤可观察集合。

13

我有一个显示可观测集合中项的 ListView 控件。这些项需要进行筛选。我可以使用 CollectionViewSource 进行筛选,但每次更改项时都需要更新筛选器。

我的项看起来像这样:

enum Status {Done, Failed, Skipped, ...}

class Project {
  public string Name {get;set;}
  public Status Status {get;set;}
  // etc. etc.
}

class ProjectViewModel : INotifyPropertyChanged {
  private Project project;

  public ProjectBuildInfoViewModel(ProjectBuildInfo project)
  {
    this.project = project;
  }

  public string Name
  {
     get { return project.Name; }
     set { project.Name = value; OnPropertyChanged("Name"); }
  }

  // etc. etc.
}

class CollectionViewModel {
  private ObservableCollection<ProjectViewModel> projects = 
             new ObservableCollection<ProjectViewModel>();

  public ObservableCollection<ProjectViewModel> Collection
  {
     get { return projects; }
     private set {projects = value; }
  } 
}

然后我有一个ListView,它的ItemSource与该集合绑定。

// member of the user control class
private CollectionViewModel collection = new CollectionViewModel();

// in the constructor
listView.ItemSource = collection.Collection.

这不会过滤任何内容。所以我有这些复选框,它们应该指示应显示哪些项目(取决于状态)。我已经使用 CollectionViewSource

private void UpdateView()
{
  var source = CollectionViewSource.GetDefaultView(collection.Collection);
  source.Filter = p => Filter((ProjectViewModel)p);
  listStatus.ItemsSource = source;
}

filter 方法的语法如下:

private bool Filter(ProjectViewModel project)
{
     return (ckFilterDone.IsChecked.HasValue && ckFilterDone.IsChecked.Value && project.Status == Status.Done) ||
            (ckFilterFailed.IsChecked.HasValue && ckFilterFailed.IsChecked.Value && project.Status == Status.Failed) ||
            (ckFilterSkipped.IsChecked.HasValue && ckFilterSkipped.IsChecked.Value && project.Status == Status.Skipped);
}

这种方法的缺点在于它捕获了复选框的值,因此每次选中一个复选框,我就必须调用这个方法 (UpdateView)。不过它能够工作。

但是,项目状态确实会改变,如果例如 "done" 没有被选中,当一个项目进入 "done" 时,它应该从视图中删除。显然,除非我再次调用 UpdateView,否则这不会改变。所以每次发生更改都需要调用此方法。这对我来说看起来很丑陋和低效。

因此,我的问题是,有没有更好的方法做到这一点?


我在回答中发布了另一种方法,但我记得可以在不调用Update()的情况下使过滤器起作用。尝试在项目中实现NotifyPropertyChanged - 如果没有它,绑定将不知道更改。 - paparazzo
3个回答

26

通过创建一个属性,将你的 ListView 直接绑定到筛选后的集合而不是 ObservableCollection 上 -

public ICollectionView YourFilteredCollection
{
   get
   {      
      var source = CollectionViewSource.GetDefaultView(collection.Collection);
      source.Filter = p => Filter((ProjectViewModel)p);
      return source;
   }
}

因此,现在您只需要在复选框状态更改事件中调用集合的Refresh()方法,就像这样-

YourFilteredCollection.Refresh();

若要基于项类的任何状态更改刷新集合,您可以通过钩取项类的PropertyChanged事件(为此,您的类需要实现INotifyPropertyChanged)将其泛化,并从那里调用refresh,如下所示 -

foreach (YourClass item in collection.Collection)
{
  item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}

void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  YourFilteredCollection.Refresh();
}

所以,无论你的项类中有哪个属性更改了,你的集合都会被过滤。


1
但是每当项目的状态或复选框发生变化时,我仍然需要调用Refresh。 - Marius Bancila
我已经更新了关于状态更改的问题的答案。这样,现在你只需要从两个地方调用刷新。希望这有所帮助。此外,你可以子类化ObservableCollection并将逻辑移动到那里,然后可以使用你的子类替换你的ObservableCollection。 - Rohit Vats
1
这可能对您有所帮助,以子类化ObservableCollection - http://msdn.microsoft.com/en-us/library/ee696421.aspx - Rohit Vats

6
我喜欢使用DataTriggers。对于您的逻辑,需要使用多值转换器。
 <ListView Grid.Row="3" Grid.Column="2" ItemsSource="{Binding Path=GabeLib.DocFieldsAll}">
        <ListView.ItemContainerStyle>
            <Style TargetType="{x:Type ListViewItem}"  >
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=Active}" Value="False">
                        <Setter Property="Visibility" Value="Collapsed"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=FieldDef.ID}" Value="0">
                        <Setter Property="Visibility" Value="Collapsed"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ListView.ItemContainerStyle>

0
使用像ContinuousLinq这样的工具。将您的列表视图绑定到一个查询,当列表中的项目(或列表本身)发生更改时将重新评估该查询。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接