树形结构在开发中的应用

树形结构在开发中的应用

撰文: 李洪根


** 本文首发于 《 CSDN ** 开发高手》2003 年第十二期

概述

TreeView是一个重要的控件,无论是在VB.NET,C# 还是VB、Delphi等各种语言中,都充当了导航器的作用。在实际工作中,很多情况下需要将TreeView与数据库进行连接,以填充其节点。在Windows Form和Web Form中,我们可以用TreeView来显示树形结构,如显示目录树、显示地区、分类显示商品等。可以说,在大部分软件的开发中,TreeView都是一个不可缺少的展示控件。因此,树形结构的设计就成了软件开发人员一个永恒的话题。

树形结构的展示方式

树形结构的展示一般来讲有三种方式:

1. 界面设计时在TreeView设计器或者代码中直接填充TreeView控件。

2. 从XML文件中建立树形结构。

3. 从数据库中得到数据,建立树形结构。

第一种方式是最简单的,这种方式主要用于树形结构一般没有变化的应用程序,在设计时就固定一颗树。当然,在设计时固定了树的结构,以后要想修改、增加、删除树的节点,就必须修改源程序。所有不利于扩展。

第二种方式从XML文件中提取,由于XML本身就是树形结构的,微软提供的文档对象模型DOM 可以方便的读取、操作和修改 XML 文档。在.NET中,应用System.Xml类可以方便地将XML文件加载到TreeView控件中,微软的MSDN也提供了实例,此处就不再多说。

第三种方式,树形结构的数据,从数据库中获得。一般来讲,我们的应用程序多数是基于数据库的。采用这种方式,增加、修改、删除一颗树的节点很方便,只要操作数据库中的数据就可以了。而且,这种方式可以和数据库中的其它表做关联、查询和汇总,通过设计视图或存储过程,很容易查询出你想要的相关数据。下面,我们主要讨论这种方式的设计和实现。

数据库设计

首先,我们在SQL SERVER 2000里建立一个表tbTree,表的结构设计如下:

列名

|

数据类型

|

描述

|

长度

|

主键

---|---|---|---|---

ID

|

Int

|

节点编号

|

4

|

ConText

|

Nvarchar

|

我们要显示的节点内容

|

50

|

ParentID

|

Int

|

父节点编号

|

4

|

Depth

|

Int

|

深度

|

4

|

关于Depth(深度)字段,主要是存放节点的层数,也就是说这个节点在树中的哪个层。有Depth(深度)字段,我们编程时会比较方便,在SQL查询时只有加一个where 条件就可以查询出当前深度的层的所有节点。如果我们不设计Depth(深度)字段,同样可以做类似的查询,这就需要在后台的SQL 查询中用循环处理。或者,你可以不在后台数据库服务器端处理,把这些处理放在前台。下面我们将介绍这几种处理方式:

在SQL SERVER 2000中建表的脚本:

CREATE TABLE [dbo].[tbTree] (

[ID] [int] IDENTITY (1, 1) NOT NULL ,

[Context] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,

[ParentID] [int] NULL

) ON [PRIMARY]

在表中添加如下记录:

SET IDENTITY_INSERT tbtree ON

insert tbtree (ID,Context,ParentID) values ( 1,' 中国 ',0)

insert tbtree (ID,Context,ParentID) values ( 2,' 北京 ',11)

insert tbtree (ID,Context,ParentID) values ( 3,' 天津 ',1)

insert tbtree (ID,Context,ParentID) values ( 4,' 河北省 ',1)

insert tbtree (ID,Context,ParentID) values ( 5,' 广东省 ',1)

insert tbtree (ID,Context,ParentID) values ( 6,' 广州 ',5)

insert tbtree (ID,Context,ParentID) values ( 7,' 四川省 ',1)

insert tbtree (ID,Context,ParentID) values ( 8,' 成都 ',7)

insert tbtree (ID,Context,ParentID) values ( 9,' 深圳 ',5)

insert tbtree (ID,Context,ParentID) values ( 10,' 石家庄 ',4)

insert tbtree (ID,Context,ParentID) values ( 11,' 辽宁省 ',1)

insert tbtree (ID,Context,ParentID) values ( 12,' 大连 ',11)

insert tbtree (ID,Context,ParentID) values ( 13,' 上海 ',1)

insert tbtree (ID,Context,ParentID) values ( 14,' 天河软件园 ',6)

insert tbtree (ID,Context,ParentID) values ( 15,' 汕头 ',5)

SET IDENTITY_INSERT tbtree off

有Depth(深度)字段时在VB6 中的实现 :

我们看一下,用ADD方法添加一个新节点到TreeView的节点集合,语法如下:

Nodes.Add(relative,[relationship][,key][,text][,image][,selectedimage])

从上面的语法,可以看出添加一个节点,只需要知道父节点编号的key,就可以通过这个key添加子节点。

如果数据库中查询出来的结果集中是按Depth (深度)列排序的话,就可以先加第一层的节点、再加第二层的节点….一直到第N层。所以,下文我写了一个AddTree函数,参数是层数(深度),RS是打开小于等于此层数的所有记录,并按层数排序。所以一层一层地添加,通过循环记录集,就可以完成一颗树。够简单吧!

Dim CN As ADODB.Connection ' 定义数据库的连接

Dim Rs As ADODB.Recordset

' 工程 ---> 引用 --->Microsoft ActiveX Data Object 2.x (版本号)

Private Sub Form_Load()

Set CN = New ADODB.Connection

‘ 连接数据库

CN.ConnectionString = "Provider=sqloledb;Data Source=pmserver;Initial Catalog=Benchmark;User Id=sa;Password=sa;"

CN.Open

Call AddTree(3)

End Sub

Private Sub AddTree(ByVal intDepth As Integer)

‘ 打开记录集,得到深度小于些深度的所有节点,并按深度排序

Set Rs = New ADODB.Recordset

Rs.Open "select * from tbTree where depth<='" & intDepth & "' order by depth", CN, adOpenDynamic, adLockReadOnly

Dim Xnod As Node

Do While Not Rs.EOF

If Rs.Fields("depth") = 0 Then

‘ 加入根结点

Set Xnod = TreeView1.Nodes.Add(, , "key" & Rs.Fields("id"), Rs.Fields("context"))

Else

‘ 加入子节点

Set Xnod = TreeView1.Nodes.Add("key" & Rs.Fields("parentid"), tvwChild, "key" & Rs.Fields("id"), Rs.Fields("context"))

End If

Xnod.EnsureVisible

Rs.MoveNext

Loop

Rs.Close

End Sub

程序运行结果如下图所示:

没有Depth(深度)时的实现

上面的程序完全是依靠Depth这一列,如果没有深度这一列来排序,可以看出,上面的代码就会出错!

从tbTree表的设计可以看出,如果没有Depth这一列,只要有ID字段和ParentID字段就可以查询到一个节点下的所有节点,答案是肯定的!看我们下面这个存储过程,其作用就是你只要传一个ID号,就可以找出下面的所有节点!而且这些节点是按层次排序的!

建立存储过程:

CREATE PROCEDURE spGetTree (

@ID int)

as

set nocount on

declare @tmp table (Id int,ConText varchar(50),ParentID int,depth int)

insert @tmp select * from tbtree where ID=@ID

while exists(select 1 from tbtree a,@tmp b where a.ParentID=b.ID and a.ID not in (select ID from @tmp))

insert @tmp select a.* from tbtree a,@tmp b where a.ParentID=b.ID and a.ID not in (select ID from @tmp)

select * from @tmp

set nocount off

GO

剖析:上面的存储过程,While语句就是一层一层地将地将树的节点插入到目的表@tmp中。有兴趣的读者可以自行跟踪一下。

我们利用上面这个存储过程,可以很容易地用VB6写出添加树状结构的代码,因为这个存储过程得到的数据是已经按层次排好序的,我们只要循环记录集,顺序添加节点就可以。

Private Sub AddTreeEx(ByVal intID As Integer)

Set Rs = New ADODB.Recordset

Rs.Open "spGettree " & intID, CN, adOpenDynamic, adLockReadOnly

Dim Xnod As Node

Do While Not Rs.EOF

If Rs.Fields("parentID") = 0 Then

Set Xnod = TreeView1.Nodes.Add(, , "key" & Rs.Fields("id"), Rs.Fields("context"))

Else

Set Xnod = TreeView1.Nodes.Add("key" & Rs.Fields("parentid"), tvwChild, "key" & Rs.Fields("id"), Rs.Fields("context"))

End If

Xnod.EnsureVisible

Rs.MoveNext

Loop

Rs.Close

End Sub

在VB.NET中实现

在.NET中,由于TreeView控件的用法和VB6中的用法是不一样的!以前的VB6程序员会因为节点没有Key属性而烦恼!在.NET中,TreeView树的节点是一个集合,每个 TreeNode 都可以包含其他 TreeNode 对象的集合。要确定您在树结构中的位置,得使用 FullPath 属性。

我们知道,添加节点只能是在找到节点之后再此节点下添加。现在VB.NET少了Key属性,对操作是一个很大的不便。微软MSDN有一篇文章用继承和重载的方法,扩展了TreeView控件,给节点加了一个key属性。有兴趣的读者可以看一下HOW TO:Create a Key Property for a TreeView Node in Visual Basic .NET这篇文章。但是美中不足的是:这篇文章只是为TreeNode加了一个NodeKey属性,但是没有提供好的Key值检索功能。尽管这一切我们都可以用代码来扩展,但是代码冗长。

所以,添加许多层节点的树形结构,只能是递归调用。而且,我们下面的代码很精炼,只需要传给递归过程一个ParentID,就会将这个编号下的所有节点加载到树形结构中! 充分体现了:简单就是好的思想。

设计思想:从数据库中查询到所有节点的记录,添加到 DataView 中,利用 DataView 的.RowFilter属性得到某个父节点编号ParentID下的所有记录,依次递归循环。

在VB.net中实现:

Private ds As New DataSet ()

' AddTree 递归函数每次都要用到数据集中的一个表,所以定义成 private

Private Sub Form1_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase .Load

' ' 定义数据库连接

Dim CN As New SqlConnection()

Try

' 初始化连接字符串

CN.ConnectionString = "data source=pmserver;initial catalog=Benchmark;persist security info=False;user id=sa;Password=sa;"

CN.Open()

' 添加命令,从数据库中得到数据

Dim sqlCmd As New SqlCommand()

sqlCmd.Connection = CN

sqlCmd.CommandText = "select * from tbtree"

sqlCmd.CommandType = CommandType.Text

Dim adp As SqlDataAdapter = New SqlDataAdapter(sqlCmd)

adp.Fill(ds)

Catch ex As Exception

MsgBox(ex.Message)

Finally

' 关闭连接

CN.Close()

End Try

' 调用递归函数,完成树形结构的生成

AddTree( 0 , Nothing )

End Sub

'̀ 递归添加树的节点

Private Sub AddTree( ByVal ParentID As Integer , ByVal pNode As TreeNode)

Dim Node As TreeNode

Dim dvTree As New DataView()

dvTree = New DataView(ds.Tables( 0 ))

' 过滤 ParentID, 得到当前的所有子节点

dvTree.RowFilter = "PARENTID = " + ParentID.ToString

Dim Row As DataRowView

For Each Row In dvTree

If pNode Is Nothing Then ' 判断是否根节点

'̀ 添加根节点

Node = TreeView1.Nodes.Add(Row( "context" ).ToString())

'̀ 再次递归

AddTree(Int32.Parse(Row( "ID" ).ToString()), Node)

Else

‘ 添加当前节点的子节点

Node = pNode.Nodes.Add(Row( "context" ).ToString())

'̀ 再次递归

AddTree(Int32.Parse(Row( "ID" ).ToString()), Node)

End If

Node.EnsureVisible()

Next

End Sub

程序运行结果如下图所示:

在C# 中实现:

有了在VB.NET中实现的代码,我们只要改成C#的语法就可以了:

DataSet ds=new DataSet();

private void Form1_Load(object sender, System.EventArgs e)

{

// 定义数据库连接

SqlConnection CN = new SqlConnection();

try

{

//初始化连接字符串

CN.ConnectionString= "data source=pmserver;initial catalog=Benchmark;persist security info=False;user id=sa;Password=sa;";

CN.Open();

//添加命令,从数据库中得到数据

SqlCommand sqlCmd= new SqlCommand();

sqlCmd.Connection = CN;

sqlCmd.CommandText = "select * from tbTree";

sqlCmd.CommandType = CommandType.Text ;

SqlDataAdapter adp = new SqlDataAdapter(sqlCmd);

adp.Fill(ds);

}

catch (Exception ex)

{

throw (ex);

}

finally

{

CN.Close();

}

//调用递归函数,完成树形结构的生成

AddTree(0, (TreeNode)null);

}

// 递归添加树的节点

public void AddTree(int ParentID,TreeNode pNode)

{

DataView dvTree = new DataView(ds.Tables[0]);

//过滤ParentID,得到当前的所有子节点

dvTree.RowFilter = "[PARENTID] = " + ParentID;

foreach(DataRowView Row in dvTree)

{

if(pNode == null)

{ //'̀添加根节点

TreeNode Node = treeView1.Nodes.Add(Row["ConText"].ToString());

AddTree(Int32.Parse(Row["ID"].ToString()),Node); //再次递归

}

else

{ //添加当前节点的子节点

TreeNode Node = pNode.Nodes.Add(Row["ConText"].ToString());

<DIV align=left

Published At
Categories with Web编程
Tagged with
comments powered by Disqus