Introduction
I will show my filterable treeview
windows control developed with C# .NET.
TreeView
shows its elements as treenode
s. So we cannot filter it like that gridlist
. It should show all parents of filtered node which contain keyword and it should be shown in hierarchy. If child node contains the filter keyword, parent nodes must be displayed even they don't contain the filter keyword. If we don't want to keep them in hierarchy, there are no difficulties to display as list. The main purpose is that filter operation musn't disrupt the appearance of hierarchy.
Background
If we want to only search the keyword, there is no need for my work because search operation can be carried out by recursive search or some other way and we can list the searched data in a basic list such as listbox
after search operation.
Technically speaking, TreeView
.NET Control uses TreeNodeCollection
to show and enumerate.
If we inherit the TreeView
control to override Node.Add()
function, we must inherit the TreeNodeCollection
class. But TreeNodeCollection
has a constructor which cannot be inherited. So we couldn't. The enthusiasts can re-create the TreeNodeCollection
from .NET Framework source code.
I followed a short and easy way to catch AddNode
event. I created my AddNode
function to catch.
Using the Code
My Control is created as generic type. I created TreeNodeEx
class to construct hierarchy and to keep non-duplicate node by using unique NodeId.TreeNodeEx
class derived from TreeNode
class. It has two extra properties which are NodeId
and ParentNodeId
.
When I want to enumerate the nodes, I use these properties. I also create the hierarchy by these properties.
If you want to extend or adjust the TreeNodeEx
class, you can do so easily. For example, you can add the Image
property and it can be shown in the treeview
. So I left it generic typed. If you implement it by concrete class, you can use it at design time. Because it is generic typed, it is not shown in Visual Studio toolbox. But I added a sample concrete typed TreeView
which is shown in Visual Studio toolbox.
NodeId
and ParentNodeId
are key values for my way. If you don't want to play with their values, you don't have to do this. Then, you must set the hierarchy for yourself like that Form3
example.
I created three FilterableTreeView
examples. There are differences in the ways of adding node.
Form1 Example
There must already exist NodeId
and ParentNodeId
which provides hierarchy.
Form2 Example
There must already exist NodeId
and ParentNodeId
which provides hierarchy.
There is one difference here. When adding the childNode
to parentNode
, this uses ParentNodeId
instead of ParentNode(TreeNodeEx)
.
In the above two examples, auto NodeId
generating feature was not used.
Form3 Example
There must be hierarchical typed collection such as TreeNodeCollection
. But it doesn't have to have NodeId
and ParentNodeId
.
For example; my hierarchical typed collection here:
public class CategoryClass
{
public string Name { get; set; }
public List<CategoryClass> SubCategories { get; set; }
}
But there is the most important thing AddNode
function must be used when adding the node for the above three ways.
FilterableTreeView SourceCode
Now I show you FilterableTreeView
source code.
For Design, it is easy to understand that it consists of one textbox
and one TreeView
control.
At first, we add all nodes to the TreeDictionary
collection when adding the nodes by using AddNode
function. When filtering tree nodes (TreeNodeEx
), we will use this collection for faster searching operation. By using this collection, we can avoid using recursive search. As you know, recursive call depth cannot be known. So it can be costly for the time.
public readonly Dictionary<int, T> TreeDictionary;
Here is FilteredList Dictionary
typed collection:
private Dictionary<int, T> FilteredList;
AddNode
function is here:
public int AddNode(int parentNodeId, T child)
{
lock (LOCK_OBJECT)
{
if (child.NodeId < 0)
{
child.NodeId = TreeDictionary.Count+1;
}
TreeDictionary.Add(child.NodeId, child);
if (parentNodeId == 0)
{
child.ParentNodeId = 0;
treeView.Nodes.Add(child);
}
else
{
T tmp = GetTNode(parentNodeId);
child.ParentNodeId = tmp.NodeId;
tmp.Nodes.Add(child);
}
return TreeDictionary.Count+1;
}
Overloaded AddNode
function is also here:
public int AddNode(T parentNode, T child)
{
lock (LOCK_OBJECT)
{
if (child.NodeId < 0)
{
child.NodeId = TreeDictionary.Count+1;
}
TreeDictionary.Add(child.NodeId, child);
if (parentNode == null || parentNode.NodeId == 0)
{
child.ParentNodeId = 0;
treeView.Nodes.Add(child);
}
else
{
child.ParentNodeId = parentNode.NodeId;
parentNode.Nodes.Add(child);
}
return TreeDictionary.Count+1;
}
}
FilterTextBox
changed event fire:
private void FilterTextBox_TextChanged(object sender, EventArgs e)
{
if (FilterTextBox.Text.Trim() == String.Empty || FilterTextBox.Text.Length < 3)
{
if (FilterTextBox.Text.Trim() == String.Empty)
{
LoadData(TreeDictionary);
}
return;
}
FilteredList = new Dictionary<int, T >();
foreach (T item in TreeDictionary.Values)
{
if (item.Text.Contains(FilterTextBox.Text) == true)
{
FilteredList.Add(item.NodeId, CloneExist(item));
}
}
for (int i = 0; i < FilteredList.Values.Count; i++)
{
T tmp = FilteredList.Values.ToList()[i];
while (true)
{
if (tmp.ParentNodeId == 0) break;
else
{
T parent = GetTNode(tmp.ParentNodeId);
if (FilteredList.ContainsKey(parent.NodeId) == false)
{
FilteredList.Add(parent.NodeId, CloneExist(parent));
}
tmp = parent;
}
}
}
LoadData(FilteredList);
}
After Filtering Operation shows it in the treeview
:
private void LoadData(Dictionary< int, T> pairs)
{
treeView.Nodes.Clear();
treeView.BeginUpdate();
foreach (T item in pairs.Values)
{
if (item.ParentNodeId == 0)
{
treeView.Nodes.Add(item);
}
else
{
T tmp = pairs.Values.Where(c => c.NodeId == item.ParentNodeId).FirstOrDefault();
if (tmp.Nodes.Contains(item) == false)
{
tmp.Nodes.Add(item);
}
}
}
treeView.EndUpdate();
treeView.ExpandAll();
}
Finally, here is the local function to clone TreeNodeEx
object:
public T CloneExist(T ex)
{
T treeNode = (T)Activator.CreateInstance(typeof(T));
treeNode.Text = ex.Text;
treeNode.NodeId = ex.NodeId;
treeNode.ParentNodeId = ex.ParentNodeId;
return treeNode;
}
Points of Interest
TreeView
control has some differences. It also is a dynamic control. It may have non-deterministic data. Its data display feature and search feature are slightly different from other controls.
I can say that if you want to add further features to the TreeView
control, you should think of re-creating your custom TreeView
because there are some limitations for inheriting the .NET TreeView
control.
Also github source code
History
- 11.11.2018: Initial version