The best way to filter TreeView nodes
What's the best/efficient way to filter Treeview
nodes开发者_开发技巧?
For example: I typed "abc" and only the nodes contained "abc" become visible. Then I typed "abcd", and I should to see the only nodes containing "abcd" text. And so on, so each time I changed filter criterion the TreeView
content also changes.
Any ideas?
If you are looking for the best performance, clone the tree, then remove all the items from the cloned tree, then simply replace the existing tree with the cloned (and filtered) one.
I also keep a backing tree that is always unfiltered.
This works well for me for quite big trees with 1000 - 2000 nodes.
I inherit TreeView to filter it (only for first level, I'm working on it):
public partial class winTree : TreeView
{
private NodesCollection allNodes = new NodesCollection();
[ReadOnly(true)]
public new NodesCollection Nodes { get { return allNodes; } }
private string filtro = string.Empty;
public String Filtro
{
get { return filtro; }
set { filtro = value; filtrarNodos(); }
}
public winTree()
{
InitializeComponent();
allNodes.NodeAdd += OnNodeAdd;
allNodes.NodeRemove += OnNodeRemove;
allNodes.NodesClear += OnNodesClear;
}
private void OnNodeAdd(object sender, EventArgs e)
{
TreeNode n = (TreeNode)sender;
if (passFilter(n))
{
base.Nodes.Add(n);
}
}
private void OnNodeRemove(object sender, EventArgs e)
{
base.Nodes.Remove((TreeNode)sender);
}
private void OnNodesClear(object sender, EventArgs e)
{
base.Nodes.Clear();
}
private void filtrarNodos()
{
this.BeginUpdate();
base.Nodes.Clear();
foreach(TreeNode n in this.Nodes)
{
if (passFilter(n))
{
base.Nodes.Add(n);
}
}
this.EndUpdate();
}
private bool passFilter(TreeNode nodo)
{
if (string.IsNullOrWhiteSpace(filtro))
{
return true;
}
else
{
return nodo.Text.ToLower().Contains(filtro.ToLower());
}
}
}
public class NodesCollection : IList<TreeNode>
{
private List<TreeNode> nodos = new List<TreeNode>();
public event EventHandler NodeAdd;
public event EventHandler NodeRemove;
public event EventHandler NodesClear;
private void OnNodeAdd(TreeNode nodo)
{
if (NodeAdd != null)
{
NodeAdd(nodo, EventArgs.Empty);
}
}
private void OnNodeRemove(TreeNode nodo)
{
if (NodeRemove != null)
{
NodeRemove(nodo, EventArgs.Empty);
}
}
private void OnNodesClear()
{
if (NodeRemove != null)
{
NodesClear(this, EventArgs.Empty);
}
}
#region IList<TreeNode>
public int IndexOf(TreeNode item)
{
return nodos.IndexOf(item);
OnNodeAdd(item);
}
public void Insert(int index, TreeNode item)
{
nodos.Insert(index, item);
OnNodeAdd(item);
}
public void RemoveAt(int index)
{
TreeNode nodo = nodos[index];
nodos.RemoveAt(index);
OnNodeRemove(nodo);
}
public TreeNode this[int index]
{
get
{
return nodos[index];
}
set
{
OnNodeRemove(nodos[index]);
nodos[index] = value;
OnNodeAdd(nodos[index]);
}
}
public void Add(TreeNode item)
{
nodos.Add(item);
OnNodeAdd(item);
}
public void Clear()
{
nodos.Clear();
OnNodesClear();
}
public bool Contains(TreeNode item)
{
return nodos.Contains(item);
}
public void CopyTo(TreeNode[] array, int arrayIndex)
{
nodos.CopyTo(array, arrayIndex);
}
public int Count
{
get { return nodos.Count(); }
}
public bool IsReadOnly
{
get { return true; }
}
public bool Remove(TreeNode item)
{
bool res = nodos.Remove(item);
if (res)
{
OnNodeRemove(item);
}
return res;
}
public IEnumerator<TreeNode> GetEnumerator()
{
return nodos.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return nodos.GetEnumerator();
}
#endregion
}
I would like to offer a better solution, the thing is that I don't want to derive from a new class and less code involved.
My solution:
Build the tree as would would normally do without filtering
Take a backup of the tree (code bellow) (I take it only if a search is being made, not just in case)
Do mess with the tree removing nodes and everything
If you need to reset the tree as initially, call _backup.Restore()
using System.Linq; using System.Windows.Forms; public class TreeViewBackup : List<TreeViewBackup> { public TreeNode Parent { get; } public TreeNodeCollection Children { get; } public TreeViewBackup(TreeNodeCollection children, TreeNode parent = null) { Parent = parent; Children = children; AddRange(Children.Cast<TreeNode>().Select(child => new TreeViewBackup(child.Nodes, child))); } public void Restore() { Children.Clear(); this.ForEach(clone => clone.Restore()); Children.AddRange(this.Select(n => n.Parent).ToArray()); } } public class Form1 { public void Filter() { _backup = new TreeViewBackup(_treeView.Nodes); _treeView.BeginUpdate(); MessWithMe(); _treeView.EndUpdate(); } public void Undo() { _treeView.BeginUpdate(); _backup.Restore(); _treeView.EndUpdate(); } }
If you loop from leaf to parent, you can find those root nodes that doesn't contain any match of the string in his leafs.
精彩评论