可过滤的 TreeView






2.72/5 (8投票s)
可过滤的 TreeView WinForm 用户控件
引言
我将展示我用C# .NET开发的,可过滤的treeview
Windows控件。
TreeView
将其元素显示为 treenode
。因此,我们无法像 gridlist
那样对其进行过滤。它应该显示包含关键字的已过滤节点的所有父节点,并且应该以层次结构显示。如果子节点包含筛选关键字,则即使父节点不包含筛选关键字,也必须显示父节点。如果我们不想将它们保留在层次结构中,则可以很容易地将其显示为列表。主要目的是筛选操作不能破坏层次结构的外观。
背景
如果我们只想搜索关键字,则不需要我的工作,因为搜索操作可以通过递归搜索或其他方式进行,并且我们可以在搜索操作后在基本列表(例如 listbox
)中列出搜索到的数据。
从技术上讲,TreeView
.NET控件使用 TreeNodeCollection
来显示和枚举。
如果我们继承 TreeView
控件以重写 Node.Add()
函数,则必须继承 TreeNodeCollection
类。但是 TreeNodeCollection
有一个无法继承的构造函数。所以我们做不到。 爱好者可以从.NET Framework源代码中重新创建 TreeNodeCollection
。
我遵循了一种简短而简单的方法来捕获 AddNode
事件。我创建了自己的 AddNode
函数来捕获。
Using the Code
我的控件是作为泛型类型创建的。 我创建了 TreeNodeEx
类来构造层次结构,并通过使用唯一的 NodeId.TreeNodeEx
类(派生自 TreeNode
类)来保持非重复节点。它具有两个额外的属性,分别是 NodeId
和 ParentNodeId
。
当我想枚举节点时,我使用这些属性。 我还通过这些属性创建层次结构。
如果您想扩展或调整 TreeNodeEx
类,您可以很容易地做到这一点。例如,您可以添加 Image
属性,它可以在 treeview
中显示。因此,我将其保留为泛型类型。如果您通过具体类来实现它,则可以在设计时使用它。因为它是泛型类型的,所以在Visual Studio工具箱中不会显示它。但是我添加了一个示例具体类型的 TreeView
,它显示在Visual Studio工具箱中。
NodeId
和 ParentNodeId
是我方法的关键值。 如果您不想使用它们的值,则不必这样做。 然后,您必须像 Form3
示例那样自己设置层次结构。
我创建了三个 FilterableTreeView
示例。 添加节点的方式有所不同。
Form1 示例
必须已经存在提供层次结构的 NodeId
和 ParentNodeId
。
Form2 示例
必须已经存在提供层次结构的 NodeId
和 ParentNodeId
。
这里有一个区别。 将 childNode
添加到 parentNode
时,此方法使用 ParentNodeId
而不是 ParentNode(TreeNodeEx)
。
在以上两个示例中,未使用自动 NodeId
生成功能。
Form3 示例
必须存在层次结构的类型集合,例如 TreeNodeCollection
。 但它不必具有 NodeId
和 ParentNodeId
。
例如; 这是我的分层类型集合
public class CategoryClass
{
public string Name { get; set; }
public List<CategoryClass> SubCategories { get; set; }
}
但是最重要的是,在以上三种方式添加节点时,必须使用 AddNode
函数。
FilterableTreeView 源代码
现在我向您展示 FilterableTreeView
源代码。
对于设计,很容易理解它由一个 textbox
和一个 TreeView
控件组成。
首先,我们使用 AddNode
函数添加节点时,将所有节点添加到 TreeDictionary
集合中。 过滤树节点(TreeNodeEx
)时,我们将使用此集合进行更快的搜索操作。 通过使用此集合,我们可以避免使用递归搜索。 如您所知,递归调用深度是未知的。 因此,对于时间而言,它可能是昂贵的。
/// <summary>
/// All treeview node will be kept in this dictionary type collection
///
/// </summary>
public readonly Dictionary<int, T> TreeDictionary;
这是 FilteredList Dictionary
类型的集合
/// <summary>
/// FilteredList keeps selected nodes from filtering operation by text
///
/// </summary>
private Dictionary<int, T> FilteredList;
AddNode
函数在这里
/// <summary>
/// AddNode function provides that adds the child node to the parent node or root of treeview
/// If you want, you may set your child node id from db by primary key or autoincrement field
/// Every node must have unique nodeid and have parentnodeid
/// </summary>
/// <param name="parentNodeId">parentNodeId is NodeId of parent node of the child node.
///If the node is root node, parent node should be null.</param>
/// <param name="child">child is currently being added node</param>
/// <returns>return NodeId of the currently being added child node</returns>
public int AddNode(int parentNodeId, T child)
{
lock (LOCK_OBJECT)//Provides multithread safe
{
if (child.NodeId < 0)
{
child.NodeId = TreeDictionary.Count+1;
}
TreeDictionary.Add(child.NodeId, child);
if (parentNodeId == 0)//Root node
{
child.ParentNodeId = 0;
treeView.Nodes.Add(child);
}
else//Child node
{
T tmp = GetTNode(parentNodeId);
child.ParentNodeId = tmp.NodeId;
tmp.Nodes.Add(child);
}
return TreeDictionary.Count+1;
}
重载的 AddNode
函数也在这里
/// <summary>
/// AddNode function provides that adds the child node to the parent node or root of treeview
/// If you want, you may set your child node id from db by primary key or autoincrement field
/// Every node must have unique nodeid and have parentnodeid
/// </summary>
/// <param name="parent">parent is parent node of the child node.
/// If the node is root node, parent node should be null.</param>
/// <param name="child">child is currently being added node</param>
/// <returns>return NodeId of the currently being added child node</returns>
public int AddNode(T parentNode, T child)
{
lock (LOCK_OBJECT)//Provides multithread safe
{
if (child.NodeId < 0)
{
child.NodeId = TreeDictionary.Count+1;
}
TreeDictionary.Add(child.NodeId, child);
if (parentNode == null || parentNode.NodeId == 0)//Root node
{
child.ParentNodeId = 0;
treeView.Nodes.Add(child);
}
else//Child node
{
child.ParentNodeId = parentNode.NodeId;
parentNode.Nodes.Add(child);
}
return TreeDictionary.Count+1;
}
}
FilterTextBox
更改事件触发
private void FilterTextBox_TextChanged(object sender, EventArgs e)
{
//Check the filtertextbox text is not null and length bigger than 3
//Also if the filtertextbox has no char, then it should show all nodes without filtering
if (FilterTextBox.Text.Trim() == String.Empty || FilterTextBox.Text.Length < 3)
{
if (FilterTextBox.Text.Trim() == String.Empty)
{
LoadData(TreeDictionary);
}
return;
}
//Create new filteredlist for Every filter text changed
//So old filteredlist will be empty
FilteredList = new Dictionary<int, T >();
//easy to check text of all nodes that contain filter text by looping
//Also, we need to add deep copy of the node to the filteredlist
//Because we shouldn't try to add a node that is already added to the treeview
foreach (T item in TreeDictionary.Values)
{
if (item.Text.Contains(FilterTextBox.Text) == true)
{
FilteredList.Add(item.NodeId, CloneExist(item));
}
}
//Now we got all nodes which contains filter text
//But we need to add all parent nodes which do not exist in the filteredlist
//Example let's assume path is Electronic\Computer\Notebook and filter text is 'book'
//Then, treeview should show fullpath of Notebook like above
//If this node is already root node, there is nothing to do
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;
}
}
}
//After populating all nodes, it is time to show nodes on the treeview
LoadData(FilteredList);
}
过滤操作后,在 treeview
中显示它
//Data to TreeView
private void LoadData(Dictionary< int, T> pairs)
{
treeView.Nodes.Clear();
treeView.BeginUpdate();//For speeding up
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();
}
最后,这是克隆 TreeNodeEx
对象的本地函数
/// <summary>
/// If you added another property you should assign them new Instance Of T at here
/// </summary>
/// <param name="ex">Currently shown node on the treeview</param>
/// <returns>new created TreeNodeEx object</returns>
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;
}
关注点
TreeView
控件有一些差异。 它也是一个动态控件。 它可能具有不确定的数据。 其数据显示功能和搜索功能与其他控件略有不同。
我可以说,如果要向 TreeView
控件添加更多功能,则应考虑重新创建自定义 TreeView
,因为继承 .NET TreeView
控件存在一些限制。
历史
- 2018年11月11日:初始版本