如何使用 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
不漂亮,我不确定树中大量项目的性能如何。
【讨论】:
【参考方案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 与大量数据集
我应该绑定到 ICollectionView 还是 ObservableCollection
CollectionViewSource、ICollectionView、ListCollectionView、IList 和 BindingListCollectionView 及其用例有啥区别?