VB.NET 中数据库快速更新 Treeview 控件





5.00/5 (8投票s)
使用数据库作为源快速更新TreeView节点的快速方法
引言
关于 .NET Treeview
控件的使用文档并不多,除了 CodeProject 上的一些。我找到的要么是关于 COM Treeview
控件的旧代码,要么是使用递归算法的文章(顺便说一句,这些算法很酷)。我需要一种快速、轻量级的方法,在用户点击时动态填充树的节点(也称为按需加载或填充)。这就是你在使用 Windows 资源管理器浏览网络共享的树时遇到的行为。除非你实际点击它,否则你不会看到可能的节点展开的指示(+ 号)。我认为这种行为对于我正在编写的应用程序来说是可接受的,因为源数据库包含数千条记录。对展开的节点进行选择性递归,或者根据查询返回的记录数来决定是否递归,可能是一个选项,我欢迎对此话题的任何评论。
我对代码进行了修改,以提前加载一个级别——算法现在加载所选节点的孙节点。这复制了 Windows 资源管理器在浏览本地驱动器时的树行为。
这是我的第一次提交,我将不尝试包含可运行的代码或项目示例,只提供代码片段来演示该方法。
本文的范围不包括教读者如何使用 ADO.NET 来完成本文所述的功能。网上有很多关于这方面的优秀文章可以学习。
背景
你可以在网上搜索处理递归方法来填充整个树的代码片段,其中一个例子在这里。请注意,作者正在使用 ASP.NET,并且在 ADO.NET 中使用了表适配器方案,我认为这对于仅仅填充节点来说有点大材小用,除非开发人员打算允许用户编辑和保存节点标签。有启发性的是用于表示文件夹关系的数据库表方案——这是我使用了一段时间的方法,并且在这篇文章中得到了演示。它非常强大,例如,你可以允许用户像 Windows 资源管理器那样拖放文件夹,而只需要在数据库中更新一条记录即可完成操作。通过事务处理,用户可以撤销操作,并且强烈建议这样做。
这是我经常用来表示分层关系的数据库表方案。
RecordID (主键) |
ParentID | 名称 |
1 | 0 | Topic 1 |
2 | 0 | Topic 2 |
3 | 1 | RE: Topic 1 |
4 | 1 | RE: Topic 1 |
5 | 2 | RE: RE: Topic 1 |
6 | 2 | RE: RE: Topic 1 |
Using the Code
这些代码片段无法直接编译。它们用于演示一种方法。
这里会做一些假设。其中一个假设是你熟悉使用 ADO.NET 打开 Access 或 SQL Server 数据库。我们将使用 IEnumerator
对象来迭代记录,填充 Treeview
节点。DataReader
和 IEnumerator
是非常快速、只读、向前走的记录集类对象,非常适合此类用途。
假设我们已经打开了一个针对数据库的查询,该查询要么读取记录来初始化 treeview
的根节点,要么使用参数或存储过程参数来填充选定节点的子节点。完成之后,我们将创建一个 SQLDbDataReader
或 OleDbDataReader
对象,并通过调用它的 GetEnumerator
来返回一个 IEnumerator
。这里我们获取一个数据枚举器来填充根节点。
Public dR As SQL(or Ole)DbDataReader
Public Function GetRootFolders() As IEnumerator
Dim dbCommandObject As SQL(or Ole)DbCommand
' specify command text and parameters, open the connection, etc.
dR = dbCommandObject.ExecuteReader()
If dR IsNot Nothing And Not dR.IsClosed Then
Return dR.GetEnumerator()
End If
Return Nothing
End Function
我们可以这样填充根节点。可以创建一个单独的节点,然后等待用户点击它——这完全是一个设计决策。
Private Sub RefreshTree()
treeView.Nodes.Clear()
Dim iE As IEnumerator = GetRootFolders()
Do While iE.MoveNext()
If iE.Current IsNot Nothing Then
Dim r As DbDataRecord = CType(iE.Current, DbDataRecord)
treeView.Nodes.Add(r("Folder_ID").ToString, _
r("DiplayText").ToString, "folderclose", "folderopen")
End If
Loop
dR.Close()
'// add children of root nodes
If treeMain.Nodes.Count > 0 Then
For Each nd As TreeNode In treeMain.Nodes
iE = pDB.GetChildFolders(CInt(nd.Name))
Do While iE.MoveNext()
If iE.Current IsNot Nothing Then
Dim r As DbDataRecord = CType(iE.Current, DbDataRecord)
If Not nd.Nodes.ContainsKey(r("Folder_ID").ToString) Then
nd.Nodes.Add(r("Folder_ID").ToString, _
r("DisplayText").ToString, "folderclose", "folderopen")
End If
End If
Loop
dR.Close()
Next
End If
End Sub
请注意丑陋的 DbDataRecord
转换。一定要测试当前记录是否为空,否则可能会抛出异常。我们使用的 Nodes.Add
方法会将记录的主键 (Folder_ID
) 放入节点的 **Name** 成员(稍后检索为 Key 或 Name),将节点的 **Text** 成员从数据库字段 "DisplayText
" 中提取出来,并指定一些来自窗体设计器中附加到 TreeView
的 ImageList
的图标。
请注意,我添加了一个 for
循环来添加根节点的子节点。for
循环正在处理的是刚刚添加的根节点集合。
因此,如果用户点击刚刚创建的根节点之一,我们会通过以下方式处理:
Private Sub treeView_NodeMouseClick(ByVal sender As Object, _
ByVal e As TreeNodeMouseClickEventArgs) Handles treeView.NodeMouseClick
Dim iE As IEnumerator
'// add the children of the children just added
If e.Node.Nodes.Count > 0 Then
For Each nd As TreeNode In e.Node.Nodes
iE = GetChildFolders(CInt(nd.Name))
Do While iE.MoveNext()
If iE.Current IsNot Nothing Then
Dim r As DbDataRecord = CType(iE.Current, DbDataRecord)
If Not nd.Nodes.ContainsKey(r("Folder_ID").ToString) Then
nd.Nodes.Add(r("Folder_ID").ToString, _
r("DisplayText").ToString, "folderclose", "folderopen")
End If
End If
Loop
dR.Close()
Next
End If
End Sub
我们从事件中获取被点击节点的 **Name** 成员,并将其用作数据库查询的参数。在 .NET treeview
控件中,一个节点在某种意义上是一个功能齐全的树,我们可以轻松地查询它的子节点。使用 ContainsKey
方法,我们检查要添加的特定节点是否已经添加。如果是,我们就跳过,否则我们就使用选定节点的 Nodes.Add
方法,就像在根文件夹代码中一样。
这里也请注意,我已经将 for
循环更改为添加所选节点的孙节点,而不是像 2010 年 4 月 6 日之前发布的代码片段中的子节点。
关注点
发现关于 .NET Treeview
控件的信息如此之少和/或理解不足,这很令人恼火。就像 COM Treeview
一样,MSDN 的文档非常糟糕。在此过程中,我学到了一些关于 .NET Treeview
控件的知识。当您需要向用户呈现分层结构时,Treeview
是不可或缺的。我发布这篇文章是为了在某种程度上回馈社区,从社区中我学到了很多关于其他事情的知识。
历史
有时间的话,我想创建一个演示项目,使用一个简单的 Access 数据库,一个 OleDbclass 或 SqlDbClass 包装类,以及继承自 BackgroundWorker
线程对象的处理程序类。
通过添加代码(2010 年 4 月 6 日),我们已经展示了在不诉诸于整个树的递归的情况下,以用户友好的方式快速更新树的能力。