我有一个包含这些数据的假设树视图:
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吗?
这是我过滤我的项目的方式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)
我发现这样做的唯一方法(有点小技巧)是创建一个从 IList 转换为 IEnumerable 的 ValueConverter。在 ConvertTo() 中,从传入的 IList 中返回一个新的 CollectionViewSource。
如果有更好的方法,我很想听听。不过,这似乎有效。
不幸的是,没有办法让相同的过滤器自动应用于所有节点。Filter 是 ItemsCollection 的一个属性(不是 DP),它不是 DependencyObject,因此不存在 DP 值继承。
树中的每个节点都有自己的 ItemsCollection,它有自己的过滤器。使其工作的唯一方法是手动将它们全部设置为调用同一个委托。
最简单的方法是在 ToolBoxViewModel 中公开 Predicate<object> 类型的 Filter 属性,并在其 setter 中触发事件。然后 ToolboxItemViewModel 将负责消费这个事件并更新它的 Filter。
不漂亮,我不确定树中大量项目的性能如何。
为什么需要过滤器或 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
| 归档时间: |
|
| 查看次数: |
22658 次 |
| 最近记录: |