2009-08-21 20 views
15

Ho una ipotetica struttura ad albero che contiene questi dati:Come filtrare una gerarchia di treeview wpf usando un ICollectionView?

RootNode 
    Leaf 
    vein 
SecondRoot 
    seeds 
    flowers 

Sto cercando di filtrare i nodi al fine di mostrare solo i nodi che contengono un certo testo. Se aggiungo "L", l'albero verrà filtrato e mostrerà solo RootNode-> Leaf e SecondRoot-> flowers (poiché entrambi contengono la lettera L).

Seguendo il modello MV-VM, ho una classe base TreeViewViewModel come questo:

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); 
     } 
    } 
    ... 
} 

E un TreeViewItemViewModel base:

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 ... 
    } 
    ... 
} 

Tutto è messa a punto nel XAML in modo da vedere la vista ad albero e casella di ricerca.

Quando si esercita questo codice, il filtro si applica solo ai nodi di radice che sono insufficienti. C'è un modo per far scorrere il filtro verso il basso nella gerarchia dei nodi in modo che il mio predicato sia chiamato per ogni nodo? In altre parole, il filtro può essere applicato al TreeView nel suo insieme?

+2

Cosa si finisce per fare ? Qualche informazione sulle prestazioni che puoi trasmettere o altra soluzione? –

risposta

3

Sfortunatamente non c'è modo di applicare automaticamente lo stesso filtro a tutti i nodi. Filter è una proprietà (non una DP) di ItemsCollection che non è DependencyObject e quindi l'ereditarietà del valore DP non esiste.

Ogni nodo dell'albero ha il proprio ItemsCollection che ha il proprio filtro. L'unico modo per farlo funzionare è impostarli manualmente tutti per chiamare lo stesso delegato.

Il modo più semplice sarebbe esporre la proprietà Filter di tipo Predicato <oggetto> nel proprio ToolBoxViewModel e nel suo setter attiva un evento. Quindi ToolboxItemViewModel sarà responsabile del consumo di questo evento e dell'aggiornamento del filtro.

Aint pretty e non sono sicuro di come sarebbe la performance per grandi quantità di oggetti nell'albero.

2

L'unico modo che ho trovato per fare questo (che è un po 'un hack), è quello di creare un ValueConverter che converte da IList a IEnumerable. in ConvertTo(), restituisce una nuova CollectionViewSource dal passato in IList.

Se c'è un modo migliore per farlo, mi piacerebbe sentirlo. Questo sembra funzionare, però.

0

È può ottenere TreeViewItem per un dato elemento in un albero usando ItemContainerGenerator e una volta che si è in grado di impostare il filtro.

6

Questo è quanto ho filtrato le voci sul mio TreeView:

ho la classe:

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; 
     }    
    } 
} 

Poi ho potuto filtrare i risultati come:

// 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");