【问题标题】:How to filter a wpf treeview hierarchy using an ICollectionView?如何使用 ICollectionView 过滤 wpf 树视图层次结构?
【发布时间】: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吗?

【问题讨论】:

  • 你最后做了什么?您可以传递任何性能信息或其他解决方案吗?

标签: c# wpf 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,一旦获得,您就可以设置过滤器。

            【讨论】:

              猜你喜欢
              • 2012-03-11
              • 2011-01-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-12-22
              • 1970-01-01
              • 2016-04-07
              • 1970-01-01
              相关资源
              最近更新 更多