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

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2010年4月3日

CPOL

5分钟阅读

viewsIcon

84248

使用数据库作为源快速更新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 节点。DataReaderIEnumerator 是非常快速、只读、向前走的记录集类对象,非常适合此类用途。

假设我们已经打开了一个针对数据库的查询,该查询要么读取记录来初始化 treeview 的根节点,要么使用参数或存储过程参数来填充选定节点的子节点。完成之后,我们将创建一个 SQLDbDataReaderOleDbDataReader 对象,并通过调用它的 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" 中提取出来,并指定一些来自窗体设计器中附加到 TreeViewImageList 的图标。

请注意,我添加了一个 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 日),我们已经展示了在不诉诸于整个树的递归的情况下,以用户友好的方式快速更新树的能力。

© . All rights reserved.