【问题标题】:Filtering Datagird with 1+ comboBoxes - multiple Filterconditions (MVVM)使用 1+ 组合框过滤 Datagrid - 多个过滤条件 (MVVM)
【发布时间】:2020-06-06 07:33:29
【问题描述】:

场景很简单,但我坚持细节:

我有一个数据网格,它绑定到我的 viewModel 中的一个可观察集合。我想用三个组合框的条目过滤我的数据网格。这里还有其他关于多个过滤器的帖子,但我找不到适合我的案例或 MVVM 的帖子。在我的研究过程中,我遇到过几次让 ControlViewSource 安静下来的方法。为简单起见,我在文本后添加了一张图片。这是我的代码: MainWindow.xaml

<Window.DataContext>
   <viewModels: MainViewModel />
</Window.DataContext>

<Window.Resources>
   <CollectionViewSource x:Key=“CVS“ Source=“{Binding FirstFilter}“/>
</Window.Resources>

<StackPanel>
   <StackPanel Orientation=“Horizontal“>
      <ComboBox x:Name=“ItemsFilter“ ItemsSource=“{Binding ItemsList}“ SelectedItem="{Binding CurrentItem}"/>
      <ComboBox x:Name=“ElementsFilter“ ItemsSource=“{Binding ElementsList}“ SelectedItem="{Binding CurrentElement}"/>
      <ComboBox x:Name=“ValuesFilter“ ItemsSource=“{Binding ValuesList}“ SelectedItem="{Binding CurrentValue}"/>
</StackPanel>

<DataGrid x:Name=“MainGrid“ ItemsSource=“{Binding DataList}“/>
</StackPanel>

我的目标是根据每个组合框中的选定项过滤数据网格中的数据。为了给你一个想法,让我们把它简单化:

  • comboBox “ItemsFilter” 具有条目 “AllItems”、“ItemsA” 和 “ItemsB”(而 A 和 B 指的是像“西瓜”和“苹果”这样的项目类别,因此所有是“所有水果”)。

  • comboBox „ElementsFilter“还决定了“ItemsFilter”的选择:它应该有类似“AllElements”、“ElementA”、“ElementB”的东西(而这可以指品牌,比如“西瓜”( ITemsFilter) 和“品牌 A” (ElementsFilter)。

  • 组合框“ValuesFilter”将是“AllValues”、“ValueA”、“ValueB”,并且可以是价格。

所以,直到现在,我只有一个过滤器可以工作。我的 viewModel 中的代码:

public ObservableCollection<DataItem> DataList {get; set;} = new     ObservableCollection<DataItem>();
public ObservableCollection<DataItem> FirstFilter {get; set;} = new     ObservableCollection<DataItem>();

private ICollectionView _myFilterView;
public ICollectionView MyFilterView
{
   get {return _myFilterView;}
   set 
   {
      _myFilterView = value;
      OnPropertyChange(nameof(MyFilterView);
   }
}

private string _currentItem;
public string CurrentItem
{
   get { return _currentItem}
   set 
   {
      _currentItem = value;
      OnPropertyChange(nameofMyFilterView);
   }
}
   // I also have properties CurrentElement, CurrentValue --> all of them representing the currently selected item, element or value (as string) in one of the associated comboBoxes.

public MainViewModel()
{
   //… loading data into my collections
   MyFilterView = CollectionViewSource.GetDefaultView(FirstFilter);
   MyFilterView.Filter = new Predicate<object>(ItemsFilter);
}

要完成这项工作,要做的最后一件事是在我的 viewModel 中安装将处理过滤器事件的方法:

private bool ItemsFilter(object o)
{
   if(string.IsNullOrWhitespace(CurrentFilter)
   {
      return true;
   }
   else
   {
      if(CurrentFilter == „All Items“)
      {
         return true;
      }
      else
      {
         DataItem d = new DataItem();

         return (d.Item == o.Item);
      }
   }
}

基本上——运行一个过滤器就是这样。 如何实现其他过滤器? 到目前为止我尝试了什么:

  • 分配另一个 ICollectionView(不起作用 - 数据网格始终为空,即使 DefaultView 设置为同一个集合)
  • 尝试在单独的方法中设置 MyFilterView.Filter,每次都设置。这个“有点”行得通,只要我的列表不是建立在分层结构上:代码变得一团糟,对于每个新的组合框,我必须编写大量嵌套的 if 语句,将它们委托给下一个检查点。 有人可以向我解释实现这一目标的简单方法吗?代码示例将非常受欢迎! 非常感谢!

【问题讨论】:

    标签: wpf filter combobox datagrid


    【解决方案1】:

    了解DataItem 的结构以及ItemsList 和其他过滤器列表的填充方式非常重要。

    无论如何,我假设 DataItem 有 3 个属性,即 string Itemstring Elementstring value,并且 ItemsList 包含 Item 字段的所有可能值 DataList 加上 @ 987654330@.

    ItemsFilter 中,您可以编写任何您想要的过滤器,那么为什么不在其中实现另外两个呢?

    private bool ItemsFilter(object o)
    {
        var dataItem = o as DataItem;
    
        // Item filter
        if(!string.IsNullOrWhitespace(CurrentFilter))
            if(CurrentFilter != "All Items")
                if (o.Item != CurrentFilter)
                    return false;
    
        // Element filter
        if(!string.IsNullOrWhitespace(CurrentElement))
            if(CurrentElement != "All Elements")
                if (o.Element != CurrentElement)
                    return false;
    
        // Value filter
        if(!string.IsNullOrWhitespace(CurrentValue))
            if(CurrentValue != "All Values")
                if (o.Value != CurrentValue)
                    return false;
    
        return true;
    }
    

    希望对你有所帮助。

    【讨论】:

    • 当时我想出了类似的东西,但它不能正常工作。原因如下:第一条语句将过滤 MyFilterView,第二条语句(根据过滤结果)再次过滤它,第三条也是如此。换句话说:我可能会丢失有效的数据条目。到目前为止,我所能发现的是,要使多个过滤器起作用,我必须实例化一个 CollectionViewSource 实例,而不是使用 CollectionViewSource.GetDefaultView() 静态方法。通过提供过滤器事件,实例化 CVS 允许多个过滤器。但是怎么做呢?
    • 对不起,你能举个例子,这个设置可能会丢失数据吗?
    • 假设我正在过滤值 B,但没有值 B。现在 MyFilterView 将为空,这将在数据网格中完美反映,但如果将过滤器更改为项目 B ,选择仍然是空的,虽然有一个Item B。我认为问题是GetDefaultView(),但是每当我实例化CVS时,我可以设置.Filter +=任何我喜欢的方法。但这使我回到了起点,我不知道如何进行。无论如何,谢谢你,费德里科!
    • 我认为你可以设置 .Filter+=method 多次,因为它是一个事件。我仍然认为你的例子的结论有问题。如果没有行的值为 B,即使行匹配任何其他过滤器,也没有显示行是正确的。当您有多个过滤器时,它们始终以“和”模式工作,因此您必须匹配所有过滤器才能通过。如果您不再需要值过滤器,可以将其设置回“所有值”。如果您对这个多重过滤器的工作方式有明确的规则,我可以帮助您,否则我很难理解您想要实现的目标。
    • Ferderico Rossi:感谢您的帮助 - 这是朝着正确方向发展的提示。看看我的回答,你会发现,它实际上要复杂得多。不能说我完全理解它,但现在它可以工作了。
    【解决方案2】:

    所以,我想出了一个同时考虑 MVVM 的解决方案。它可能不是最优雅的,但也许这可以看作是一个可以重构和改进的 kickstart。 首先:您需要实例化 CollectionViewSource 而不是安装 ICollectionView。因此,在您的 viewModel 中:

    public CollectionViewSource _primaryView;
    public CollectionViewSource PrimaryView
    {
       get {return _primaryView; }
       set
       {
       _primaryView = value;
       PropertyChange(nameof(_primaryView);
       }
    }
    

    同时实例化您想要过滤的项目集合。就我而言:

    //This is the collection you want to filter. In my case it holds fields for  
    //objects of type item, element and value.
    
    public ObservableCollection<FilterObjects> DefaultFilter {get; set;} = new     
    ObservableCollection<FilterObject>();
    

    然后,您需要将 PrimaryView 的源设置为您的集合(您要过滤的那个)。我们在 viewModel 的构造函数中设置它。所以就我而言:

    public MainViewModel()
    {
       //Here I fill my dummy data to my collection I want to filter
       // -- load dummy data --
    
       //Then set PrimaryView.Source to the collection you want to filter
       PrimaryView.Source = DefaultFilter;
    }
    

    接下来,设置您的过滤器。因此,您的视图中需要两个组合框。像这样:

    <ComboBox x:Name="cmbItemFilter" Margin="10" ItemsSource="{Binding ItemsList}"    
    SelectedIndex="0" SelectedItem="{Binding CurrentItem}">                    
    </ComboBox>
    

    ItemsList 只是我的 viewModel 中的另一个属性,基本上是项目对象的列表。 CurrentItem 是一个字符串类型的属性,它保存组合框的当前选定项。不要忘记在这里提出 PropertyChange:

    private string _currentItem;
    public string CurrentItem
    {
    get { return _currentItem; }
    set
    {
       _currentItem = value;
    
       PropertyChange(nameof(_currentItem));
    }
    

    }

    对您可能用作过滤器的任何其他组合框进行相应操作。所以,就我而言,我还有两个组合框,绑定到元素类型的列表和值类型的列表。同样,每个组合框的选定项都绑定到我的 viewModel 中的一个属性(CurrentElement,CurrentValue)。 看到您的视图的 dataContext 设置为您的视图模型。我喜欢在 XAML 中这样做,所以这里是:

    <Window.DataContext>
    <viewModels:MainViewModel/>
    </Window.DataContext>
    

    ViewModels 是一个命名空间,需要导入到我的视图语句中。这很简单,但如果您不熟悉它:将所有视图模型放在解决方案中名为“ViewModels”的文件夹中。现在您需要在 XAML 中引用此文件夹,如下所示:

    <Window x:Class="DataGrid_Templating.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    …
    xmlns:viewModels ="clr-namespace:[nameOfYourProjectWithoutRectangularBrackets].    
    [FoldernameWhereYourViewModels resideWithoutRectangularBrackets]"  
    

    您在这里要做的最后一件事是显示您的数据网格从哪里获取数据。因此,您需要按如下方式设置 itemsSource:

    <DataGrid x:Name="dtgMainGrid" ItemsSource="{Binding PrimaryView.View}"  
    HorizontalContentAlignment="Center"> </DataGrid>
    

    确保您绑定的是 CollectionViewSource 的视图属性 - 否则它将无法正常工作!

    从现在开始,您所要做的就是设置过滤器逻辑。男孩,这给了我噩梦,但它有效!所以,再一次,转到您的 viewModel,并在您的 CurrentItem (CurrentElement / CurrentValue) 属性中更改它:

    private string _currentItem;
    public string CurrentItem
    {
       get { return _currentItem; }
       set
           {
             _currentItem = value;
    
             if (CurrentValue == „Show all Items“)
           {
             ApplyElementFilter();
             ApplyValueFilter();
           }
             else
           {
             ApplyItemFilter();
           }       
    
             PropertyChange(nameof(_currentItem));
         }
    }
    

    这部分非常棘手,我仍然不确定,这里发生了什么——我或多或少偶然地得到了想要的结果……只要确保你有一个过滤条件,比如“选择所有人”或“显示所有类别”(有点像默认设置),它将启动您拥有的所有其他过滤器 看下一段,我相信代码对此很清楚。 ApplyElementFilter() 和 ApplyValueFilter(或您喜欢的任何其他过滤器)必须在您的 viewModel 中实现:

    private void ApplyItemFilter()
    {
       PrimaryView.Filter -= new FilterEventHandler(FilterByItems);
       PrimaryView.Filter += new FilterEventHandler(FilterByItems);           
    }
    
    private void ApplyElementFilter()
    {
       PrimaryView.Filter -= new FilterEventHandler(FilterByElement);
       PrimaryView.Filter += new FilterEventHandler(FilterByElement);            
    }
    
    private void ApplyValueFilter()
    {
       PrimaryView.Filter -= new FilterEventHandler(FilterByValue);
       PrimaryView.Filter += new FilterEventHandler(FilterByValue);            
    }
    

    你已经接近它了,所以请耐心等待。您要做的最后一件事是分配您在 FilterEventHandler 中引用的方法。它们都是一样的,所以为了简单起见,我只提到一个:

    public void FilterByItems(object sender, FilterEventArgs e)
    

    { var src = e.Item as Item;

       if (src == null)
       {
          e.Accepted = false;
          else if (CurrentItem== "Show all Items")
          e.Accepted = true;
       }
       else if (string.Compare(CurrentItem, src.CurrentItem) != 0)
       {
          e.Accepted = false;           
       }
    

    一开始要吞下很多东西,但正如我之前所说:我确信有办法获得这个功能,并且更优雅地摆脱它。希望有人可以利用这个。 干杯

    【讨论】:

      猜你喜欢
      • 2017-09-02
      • 1970-01-01
      • 1970-01-01
      • 2020-06-07
      • 1970-01-01
      • 1970-01-01
      • 2013-01-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多