如何使用 ICollectionView 过滤 wpf 树视图层次结构?

Posted

技术标签:

【中文标题】如何使用 ICollectionView 过滤 wpf 树视图层次结构?【英文标题】:How to filter a wpf treeview hierarchy using an ICollectionView? 【发布时间】:2010-11-21 17:59:25 【问题描述】:

我有一个包含这些数据的假设树视图:

RootNode
   Leaf
   vein
SecondRoot
   seeds
   flowers

我正在尝试过滤节点以仅显示包含特定文本的节点。假设我指定“L”,树将被过滤并仅显示 RootNode->Leaf 和 SecondRoot->flowers(因为它们都包含字母 L)。

按照 m-v-vm 模式,我有一个基本的 TreeViewViewModel 类,如下所示:

public class ToolboxViewModel

    ...
    readonly ObservableCollection<TreeViewItemViewModel> _treeViewItems = new ObservableCollection<TreeViewItemViewModel>();
    public ObservableCollection<TreeViewItemViewModel> Headers
    
        get  return _treeViewItems; 
    

    private string _filterText;
    public string FilterText
    
        get  return _filterText; 
        set
        
            if (value == _filterText)
                return;

            _filterText = value;

            ICollectionView view = CollectionViewSource.GetDefaultView(Headers);
            view.Filter = obj => ((TreeViewItemViewModel)obj).ShowNode(_filterText);
        
    
    ...

还有一个基本的 TreeViewItemViewModel:

public class ToolboxItemViewModel

    ...
    public string Name  get; private set; 
    public ObservableCollection<TreeViewItemViewModel> Children  get; private set; 
    public bool ShowNode(string filterText)
    
        ... return true if filterText is contained in Name or has children that contain filterText ... 
     
    ...

一切都在 xaml 中设置,所以我看到了树视图和搜索框。

执行此代码时,过滤器仅适用于不足的根节点。有没有办法让过滤器在节点的层次结构中向下渗透,以便为每个节点调用我的谓词?换句话说,过滤器可以应用于整个TreeView吗?

【问题讨论】:

你最后做了什么?您可以传递任何性能信息或其他解决方案吗? 【参考方案1】:

这就是我在TreeView 上过滤项目的方式:

我有课:

class Node

    public string Name  get; set; 
    public List<Node> Children  get; set; 

    // this is the magic method!
    public Node Search(Func<Node, bool> predicate)
    
         // if node is a leaf
         if(this.Children == null || this.Children.Count == 0)
         
             if (predicate(this))
                return this;
             else
                return null;
         
         else // Otherwise if node is not a leaf
         
             var results = Children
                               .Select(i => i.Search(predicate))
                               .Where(i => i != null).ToList();

             if (results.Any())
                var result = (Node)MemberwiseClone();
                result.Items = results;
                return result;
             
             return null;
                      
    

然后我可以将结果过滤为:

// initialize Node root
// pretend root has some children and those children have more children
// then filter the results as:
var newRootNode = root.Search(x=>x.Name == "Foo");

【讨论】:

【参考方案2】:

不幸的是,无法将相同的过滤器自动应用于所有节点。 Filter 是 ItemsCollection 的一个属性(不是 DP),它不是 DependencyObject,因此不存在 DP 值继承。

树中的每个节点都有自己的 ItemsCollection,它有自己的过滤器。使其工作的唯一方法是手动将它们全部设置为调用同一个委托。

最简单的方法是在 ToolBoxViewModel 上公开 Predicate 类型的 Filter 属性,并在其设置器中触发一个事件。然后 ToolboxItemViewModel 将负责消费这个事件并更新它的过滤器。

不漂亮,我不确定树中大量项目的性能如何。

【讨论】:

【参考方案3】:

我发现这样做的唯一方法(这有点小技巧)是创建一个将 IList 转换为 IEnumerable 的 ValueConverter。在 ConvertTo() 中,从传入的 IList 中返回一个新的 CollectionViewSource。

如果有更好的方法,我很乐意听到。不过,这似乎可行。

【讨论】:

【参考方案4】:

为什么需要过滤器或 CollectionSource?这是处理 TreeView 项目的简单 MVVM 方法。

您可以使项目可见、折叠、更改颜色、突出显示、闪烁等等,只需使用 DataTriggers:

public class Item : INotifyPropertyChanged

    public string Title                      get; set;  // TODO: Notify on change
    public bool VisibleSelf                  get; set;  // TODO: Notify on change
    public bool VisibleChildOrSelf           get; set;  // TODO: Notify on change
    public ObservableCollection<Item> Items  get; set;  // TODO: Notify on change

    public void CheckVisibility(string searchText)
    
         VisibleSelf = // Title contains SearchText. You may use RegEx with wildcards
         VisibleChildOrSelf = VisibleSelf;

         foreach (var child in Items)
         
             child.CheckVisibility(searchText);
             VisibleChildOrSelf |= child.VisibleChildOrSelf;
         
    


public class ViewModel : INotifyPropertyChanged

    public ObservableCollection<Item> Source  get; set;  // TODO: Notify on change
    public string SearchText                  get; set;  // TODO: Notify on change

    private void OnSearchTextChanged()  // TODO: Action should be delayed by 500 millisec
    
        foreach (var item in Source) item.CheckVisibility(SearchText);
    


<StackPanel>
    <TextBox Text="Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" 
                 MinWidth="200" Margin="5"/>

    <TreeView ItemsSource="Binding Source" Margin="5">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="Binding Items">
                <TextBlock Text="Binding Title" />
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
        <TreeView.ItemContainerStyle>
            <Style TargetType="Control">
                <Style.Triggers>
                    <DataTrigger Binding="Binding VisibleChildOrSelf" Value="false">
                        <Setter Property="Visibility" Value="Collapsed"/>
                    </DataTrigger>
                    <DataTrigger Binding="Binding VisibleSelf" Value="false">
                        <Setter Property="Foreground" Value="Gray"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TreeView.ItemContainerStyle>
    </TreeView>
<StackPanel>

我将把完整的示例包含到我的 WPF 库中:

https://www.codeproject.com/Articles/264955/WPF-MichaelAgroskin

【讨论】:

【参考方案5】:

我决定使用此处提到的 Philipp Sumi 的树视图:http://www.codeproject.com/KB/WPF/versatile_treeview.aspx

并对其应用过滤器,如下所示:http://www.hardcodet.net/2008/02/programmatically-filtering-the-wpf-treeview

我不能推荐它:)

【讨论】:

【参考方案6】:

您可以使用 ItemContainerGenerator 获取树中给定元素的 TreeViewItem,一旦获得,您就可以设置过滤器。

【讨论】:

以上是关于如何使用 ICollectionView 过滤 wpf 树视图层次结构?的主要内容,如果未能解决你的问题,请参考以下文章

WPF 的 ICollectionView.filter 与大量数据集

ComboBox过滤

我应该绑定到 ICollectionView 还是 ObservableCollection

获取 ICollectionView 的 Count 属性

CollectionViewSource、ICollectionView、ListCollectionView、IList 和 BindingListCollectionView 及其用例有啥区别?

获取ICollectionView的Count属性