65.9K
CodeProject 正在变化。 阅读更多。
Home

可过滤的 TreeView

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.72/5 (8投票s)

2018年11月11日

CPOL

4分钟阅读

viewsIcon

15202

downloadIcon

119

可过滤的 TreeView WinForm 用户控件

filtering treeview control

引言

我将展示我用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 类)来保持非重复节点。它具有两个额外的属性,分别是 NodeIdParentNodeId

当我想枚举节点时,我使用这些属性。 我还通过这些属性创建层次结构。

如果您想扩展或调整 TreeNodeEx 类,您可以很容易地做到这一点。例如,您可以添加 Image 属性,它可以在 treeview 中显示。因此,我将其保留为泛型类型。如果您通过具体类来实现它,则可以在设计时使用它。因为它是泛型类型的,所以在Visual Studio工具箱中不会显示它。但是我添加了一个示例具体类型的 TreeView,它显示在Visual Studio工具箱中。

NodeIdParentNodeId 是我方法的关键值。 如果您不想使用它们的值,则不必这样做。 然后,您必须像 Form3 示例那样自己设置层次结构。

我创建了三个 FilterableTreeView 示例。 添加节点的方式有所不同。

Form1 示例

必须已经存在提供层次结构的 NodeIdParentNodeId

Form2 示例

必须已经存在提供层次结构的 NodeIdParentNodeId

这里有一个区别。 将 childNode 添加到 parentNode 时,此方法使用 ParentNodeId 而不是 ParentNode(TreeNodeEx)

在以上两个示例中,未使用自动 NodeId 生成功能。

Form3 示例

必须存在层次结构的类型集合,例如 TreeNodeCollection。 但它不必具有 NodeIdParentNodeId

例如; 这是我的分层类型集合

  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 控件存在一些限制。

github 源代码也在这里

历史

  • 2018年11月11日:初始版本
© . All rights reserved.