| |||||||||||||||||||||||||
树形结构在开发中的应用
撰文: 李洪根
本文首发于《优快云开发高手》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,表的结构设计如下:
关于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
在.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());
AddTree(Int32.Parse(Row["ID"].ToString()),Node); //再次递归
}
}
}
后记:请读者自行修改程序中的连接字符串设置。
附:相关微软MSDN文档,包括在VB6和.NET中从XML建立树形结构
声明:本文版权与解释权归李洪根所有,如需转载,请保留完整的内容及此声明。
QQ: 21177563
MAIL:
lihonggen0@gci-corp.com
***************************************************************
|
树形结构在开发中的应用 thx: 李洪根
最新推荐文章于 2025-02-18 08:33:47 发布