您的位置:首页 > 其它

树形结构在开发中的应用

2008-01-11 00:38 435 查看
[align=center]树形结构在开发中的应用[/align]
[align=center]撰文: 李洪根[/align]
本文首发于《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
[align=left]是[/align]
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中实现:

[align=left] Private ds As New DataSet ()[/align]
[align=left]' AddTree递归函数每次都要用到数据集中的一个表,所以定义成private[/align]
[align=left] Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load[/align]
[align=left] ' '定义数据库连接[/align]
[align=left] Dim CN As New SqlConnection()[/align]
[align=left] Try[/align]
[align=left] '初始化连接字符串[/align]
[align=left] CN.ConnectionString = "data source=pmserver;initial catalog=Benchmark;persist security info=False;user id=sa;Password=sa;"[/align]
[align=left] CN.Open()[/align]
[align=left] '添加命令,从数据库中得到数据[/align]
[align=left] Dim sqlCmd As New SqlCommand()[/align]
[align=left] sqlCmd.Connection = CN[/align]
[align=left] sqlCmd.CommandText = "select * from tbtree"[/align]
[align=left] sqlCmd.CommandType = CommandType.Text[/align]
[align=left] Dim adp As SqlDataAdapter = New SqlDataAdapter(sqlCmd)[/align]
[align=left] adp.Fill(ds)[/align]
[align=left] Catch ex As Exception[/align]
[align=left] MsgBox(ex.Message)[/align]
[align=left] Finally[/align]
[align=left] '关闭连接[/align]
[align=left] CN.Close()[/align]
[align=left] End Try[/align]
[align=left] '调用递归函数,完成树形结构的生成[/align]
[align=left] AddTree(0, Nothing)[/align]
[align=left] End Sub[/align]
[align=left] [/align]
[align=left] '̀递归添加树的节点[/align]
[align=left] Private Sub AddTree(ByVal ParentID As Integer, ByVal pNode As TreeNode)[/align]
[align=left] Dim Node As TreeNode[/align]
[align=left] Dim dvTree As New DataView()[/align]
[align=left] dvTree = New DataView(ds.Tables(0))[/align]
[align=left] '过滤ParentID,得到当前的所有子节点[/align]
[align=left] dvTree.RowFilter = "PARENTID = " + ParentID.ToString[/align]
[align=left] [/align]
[align=left] Dim Row As DataRowView[/align]
[align=left] For Each Row In dvTree[/align]
[align=left] If pNode Is Nothing Then '判断是否根节点[/align]
[align=left] '̀添加根节点[/align]
[align=left] Node = TreeView1.Nodes.Add(Row("context").ToString())[/align]
[align=left] '̀再次递归[/align]
[align=left] AddTree(Int32.Parse(Row("ID").ToString()), Node)[/align]
[align=left] Else[/align]
[align=left] ‘添加当前节点的子节点[/align]
[align=left] Node = pNode.Nodes.Add(Row("context").ToString())[/align]
[align=left] '̀再次递归[/align]
[align=left] AddTree(Int32.Parse(Row("ID").ToString()), Node)[/align]
[align=left] End If[/align]
[align=left] Node.EnsureVisible()[/align]
[align=left] Next[/align]
End Sub
程序运行结果如下图所示:



在C# 中实现:
有了在VB.NET中实现的代码,我们只要改成C#的语法就可以了:
[align=left] DataSet ds=new DataSet();[/align]
[align=left] private void Form1_Load(object sender, System.EventArgs e)[/align]
[align=left] {[/align]
[align=left] // 定义数据库连接[/align]
[align=left] SqlConnection CN = new SqlConnection();[/align]
[align=left] try [/align]
[align=left] {[/align]
[align=left] //初始化连接字符串[/align]
[align=left] CN.ConnectionString= "data source=pmserver;initial catalog=Benchmark;persist security info=False;user id=sa;Password=sa;";[/align]
[align=left] CN.Open();[/align]
[align=left] //添加命令,从数据库中得到数据[/align]
[align=left] SqlCommand sqlCmd= new SqlCommand();[/align]
[align=left] sqlCmd.Connection = CN;[/align]
[align=left] sqlCmd.CommandText = "select * from tbTree";[/align]
[align=left] sqlCmd.CommandType = CommandType.Text ;[/align]
[align=left] SqlDataAdapter adp = new SqlDataAdapter(sqlCmd);[/align]
[align=left] adp.Fill(ds);[/align]
[align=left] }[/align]
[align=left] catch (Exception ex)[/align]
[align=left] {[/align]
[align=left] throw (ex); [/align]
[align=left] }[/align]
[align=left] finally [/align]
[align=left] {[/align]
[align=left] CN.Close();[/align]
[align=left] }[/align]
[align=left] //调用递归函数,完成树形结构的生成[/align]
[align=left] AddTree(0, (TreeNode)null);[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] // 递归添加树的节点[/align]
[align=left] public void AddTree(int ParentID,TreeNode pNode) [/align]
[align=left] {[/align]
[align=left] DataView dvTree = new DataView(ds.Tables[0]);[/align]
[align=left] //过滤ParentID,得到当前的所有子节点[/align]
[align=left] dvTree.RowFilter = "[PARENTID] = " + ParentID;[/align]
[align=left] foreach(DataRowView Row in dvTree) [/align]
[align=left] {[/align]
[align=left] if(pNode == null) [/align]
[align=left] { //'̀添加根节点[/align]
[align=left] TreeNode Node = treeView1.Nodes.Add(Row["ConText"].ToString());[/align]
[align=left] AddTree(Int32.Parse(Row["ID"].ToString()),Node); //再次递归[/align]
[align=left] } [/align]
[align=left] else [/align]
[align=left] { //添加当前节点的子节点[/align]
[align=left] TreeNode Node = pNode.Nodes.Add(Row["ConText"].ToString());[/align]
[align=left] AddTree(Int32.Parse(Row["ID"].ToString()),Node); //再次递归[/align]
[align=left] }[/align]
[align=left] } [/align]
[align=left] } [/align]

后记:请读者自行修改程序中的连接字符串设置。
附:相关微软MSDN文档,包括在VB6和.NET中从XML建立树形结构

http://support.microsoft.com/default.aspx?kbid=311318
http://support.microsoft.com/default.aspx?kbid=308063
http://support.microsoft.com/default.aspx?kbid=317597
http://support.microsoft.com/default.aspx?kbid=244954

声明:本文版权与解释权归李洪根所有,如需转载,请保留完整的内容及此声明。
QQ: 21177563
MSN: lihonggen@hotmail.com
MAIL: lihonggen0@gci-corp.com
专栏: http://www.csdn.net/develop/author/netauthor/lihonggen0/
***************************************************************

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=13648
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: