Как фильтровать иерархию дерева wpf с помощью ICollectionView?
У меня есть гипотетическое древовидное представление, содержащее эти данные:
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, поэтому я вижу окно дерева и поиска.
При выполнении этого кода фильтр применяется только к корневым узлам, которых недостаточно. Есть ли способ заставить фильтр просачиваться в иерархию узлов, чтобы мой предикат вызывался для каждого node? Другими словами, может ли фильтр применяться к TreeView в целом?
Ответы
Ответ 1
К сожалению, нет способа сделать тот же фильтр применимым ко всем узлам автоматически. Фильтр - это свойство (а не DP) элемента ItemsCollection, которое не является DependencyObject, и поэтому наследование DP Value отсутствует.
Каждый node в дереве имеет свой собственный ItemsCollection, у которого есть свой собственный Фильтр. Единственный способ заставить его работать - вручную настроить их для вызова того же делегата.
Простейшим способом было бы выставить свойство фильтра типа Predicate <object> на вашем инструменте ToolBoxViewModel и в его сеттере загорится событие. Тогда ToolboxItemViewModel будет отвечать за потребление этого события и обновление его фильтра.
Aint довольно, и я не уверен, что производительность будет выглядеть для большого количества элементов в дереве.
Ответ 2
Вот как я отфильтровал элементы на моем 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");
Ответ 3
Единственный способ, которым я нашел это (что немного взломать), - создать ValueConverter, который преобразуется из IList в IEnumerable. в ConvertTo(), возвратите новый CollectionViewSource из переданного в IList.
Если есть лучший способ сделать это, я бы хотел это услышать. Это, похоже, работает.
Ответ 4
Я решил использовать древовидное изображение Филиппа Суми, упомянутое здесь: http://www.codeproject.com/KB/WPF/versatile_treeview.aspx
И применил к нему фильтр, как показано ниже: http://www.hardcodet.net/2008/02/programmatically-filtering-the-wpf-treeview
Я не мог рекомендовать его достаточно.
Ответ 5
Вы можете получить TreeViewItem для данного элемента в дереве с помощью ItemContainerGenerator
и после того, как у вас есть возможность установить фильтр.