2009-08-21 2 views
15

У меня есть гипотетический вид дерева, который содержит эти данные:Как фильтровать иерархию дерева wpf с помощью ICollectionView?

RootNode 
    Leaf 
    vein 
SecondRoot 
    seeds 
    flowers 

Я пытаюсь фильтровать узлы для того, чтобы показать только те узлы, которые содержат определенный текст. Скажем, если я укажу «L», дерево будет фильтроваться и отображать только RootNode-> Leaf и SecondRoot-> flowers (потому что они оба содержат букву L).

Следуя схеме мв-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 и окно поиска.

При выполнении этого кода фильтр применяется только к корневым узлам, которых недостаточно. Есть ли способ заставить фильтр просачиваться в иерархию узлов, чтобы мой предикат вызывался для каждого узла? Другими словами, может ли фильтр применяться к TreeView в целом?

+2

Что вы в конечном итоге делает ? Любая информация о производительности, которую вы можете передать или другое решение? –

ответ

3

К сожалению, нет способа сделать тот же фильтр применимым ко всем узлам автоматически. Фильтр - это свойство (а не DP) элемента ItemsCollection, которое не является DependencyObject, и поэтому наследование DP Value отсутствует.

Каждый узел в дереве имеет свой собственный элемент ItemsCollection, который имеет свой собственный фильтр. Единственный способ заставить его работать - вручную настроить их для вызова одного и того же делегата.

Простейший способ заключается в том, чтобы выставить свойство фильтра типа Predicate < объекта > на вашем ToolBoxViewModel и в его сеттере запустить событие. Тогда ToolboxItemViewModel будет нести ответственность за потребление этого события и обновление его фильтра.

Aint довольно, и я не уверен, что производительность будет выглядеть для большого количества предметов в дереве.

2

Единственный способ, которым я нашел это (что немного взломать), заключается в создании ValueConverter, который преобразуется из IList в IEnumerable. в ConvertTo() возвратите новый CollectionViewSource из переданного в IList.

Если есть лучший способ сделать это, я бы хотел его услышать. Это, похоже, работает.

0

Вы может получить TreeViewItem для данного элемента в дереве, используя ItemContainerGenerator, и как только у вас есть возможность установить фильтр.

6

Это, как я фильтруются детали на моем 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"); 
Смежные вопросы