Как фильтровать иерархию дерева 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.

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

Ответ 5

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