树形结构在开发中的应用[转贴]

 最近在做树形结构开发的应用,在网上找到一篇文章,特转贴过来,大家一起学习。

树形结构在开发中的应用

撰文: 李洪根 

概述

    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建立树形结构

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/
*************************************************************** 


posted on 2005-04-27 12:23  高血压的熊  阅读(924)  评论(0编辑  收藏  举报