树形结构在开发中的应用 thx: 李洪根

标题  树形结构在开发中的应用     选择自 lihonggen0 的 Blog
关键字  树、TreeView
出处 
树形结构在开发中的应用
撰文: 李洪根

本文首发于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());
                                   AddTree(Int32.Parse(Row["ID"].ToString()),Node);     //再次递归
                            }
                     }                  
              }           
 
后记:请读者自行修改程序中的连接字符串设置。
附:相关微软MSDN文档,包括在VB6和.NET中从XML建立树形结构
声明:本文版权与解释权归李洪根所有,如需转载,请保留完整的内容及此声明。
QQ: 21177563  
*************************************************************** 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值