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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1313325/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 15:00:12  来源:igfitidea点击:

How to filter a wpf treeview hierarchy using an ICollectionView?

c#wpftreeview

提问by David

I have a hypothetical tree view that contains this data:

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

RootNode
   Leaf
   vein
SecondRoot
   seeds
   flowers

I am trying to filter the nodes in order to show only the nodes that contain a certain text. Say if I specify "L", the tree will be filtered and show only RootNode->Leaf and SecondRoot->flowers (because they both contain the letter L).

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

Following the m-v-vm pattern, I have a basic TreeViewViewModel class like this:

遵循 mv-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);
        }
    }
    ...
}

And a basic TreeViewItemViewModel:

还有一个基本的 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 ... 
    } 
    ...
}

Everything is setup in the xaml so I see the treeview and search box.

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

When this code is exercised, the filter only applies to the Root nodes which is insufficient. Is there a way to make the filter trickle down in the hierarchy of nodes so that my predicate is called for every node ? In other words, can the filter be applied to the TreeView as a whole ?

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

采纳答案by Alex_P

Unfortunately there is no way to make same Filter apply to all nodes automatically. Filter is a property (not a DP) of ItemsCollection which is not DependencyObject and so DP Value inheritance isn't there.

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

Each node in the tree has its own ItemsCollection which has its own Filter. The only way to make it work is to manually set them all to call the same delegate.

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

Simplest way would be to expose Filter property of type Predicate<object> at your ToolBoxViewModel and in its setter fire an event. Then ToolboxItemViewModel will be responsible for consuming this event and updating its Filter.

最简单的方法是在 ToolBoxViewModel 中公开 Predicate<object> 类型的 Filter 属性,并在其 setter 中触发事件。然后 ToolboxItemViewModel 将负责消费这个事件并更新它的 Filter。

Aint pretty and I'm not sure what the performance would be like for large amounts of items in the tree.

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

回答by Andy

The only way I've found to do this (which is a bit of a hack), is to create a ValueConverter that converts from IList to IEnumerable. in ConvertTo(), return a new CollectionViewSource from the passed in IList.

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

If there's a better way to do it, I'd love to hear it. This seems to work, though.

如果有更好的方法,我很想听听。不过,这似乎有效。

回答by basarat

I decided to use the treeview by Philipp Sumi mentioned here : http://www.codeproject.com/KB/WPF/versatile_treeview.aspx

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

And applied a filter to it as shown here : http://www.hardcodet.net/2008/02/programmatically-filtering-the-wpf-treeview

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

I couldn't recommend it enough :)

我不能推荐它:)

回答by Peter Wone

You can get the TreeViewItem for a given element in a tree using ItemContainerGeneratorand once you have that you are in a position to set the Filter.

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

回答by Tono Nam

This is how I filtered the items on my TreeView:

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

I have the class:

我有课:

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;
         }             
    }
}

Then I could filter results as:

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

// 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");

回答by agroskin

Why do you need filters or CollectionSource? Here is a simple MVVM way to handle TreeView items.

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

You can make items visible, collapsed, change color, highlight, flash, whatever, simply by using DataTriggers:

只需使用 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>

I'm going to include the complete example into my WPF library:

我要将完整的示例包含在我的 WPF 库中:

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

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