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

Dav*_*vid 21 c# wpf treeview

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

RootNode
   Leaf
   vein
SecondRoot
   seeds
   flowers
Run Code Online (Sandbox Code Playgroud)

我试图过滤节点,以便只显示包含特定文本的节点.假如我指定"L",树将被过滤并仅显示RootNode-> Leaf和SecondRoot-> flowers(因为它们都包含字母L).

在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);
        }
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

一个基本的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 ... 
    } 
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

Ton*_*Nam 7

这是我过滤我的项目的方式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;
         }             
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我可以过滤结果:

// 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");
Run Code Online (Sandbox Code Playgroud)


And*_*ndy 5

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

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


Ale*_*x_P 5

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

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

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

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


agr*_*kin 5

为什么需要过滤器或 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>
Run Code Online (Sandbox Code Playgroud)

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

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