會員登錄 - 用戶注冊 - 網(wǎng)站地圖 Office中國(office-cn.net),專業(yè)Office論壇

樹形結(jié)構(gòu)在開發(fā)中的應(yīng)用

時間:2005-08-16 11:11 來源:office中國 作者:李洪根 閱讀:
概述
TreeView是一個重要的控件,無論是在VB.NET,C# 還是VB、Delphi等各種語言中,都充當了導(dǎo)航器的作用。在實際工作中,很多情況下需要將TreeView與數(shù)據(jù)庫進行連接,以填充其節(jié)點。在Windows Form和Web Form中,我們可以用TreeView來顯示樹形結(jié)構(gòu),如顯示目錄樹、顯示地區(qū)、分類顯示商品等。可以說,在大部分軟件的開發(fā)中,TreeView都是一個不可缺少的展示控件。因此,樹形結(jié)構(gòu)的設(shè)計就成了軟件開發(fā)人員一個永恒的話題。
樹形結(jié)構(gòu)的展示方式
樹形結(jié)構(gòu)的展示一般來講有三種方式:
1. 界面設(shè)計時在TreeView設(shè)計器或者代碼中直接填充TreeView控件。
2. 從XML文件中建立樹形結(jié)構(gòu)。
3. 從數(shù)據(jù)庫中得到數(shù)據(jù),建立樹形結(jié)構(gòu)。
第一種方式是最簡單的,這種方式主要用于樹形結(jié)構(gòu)一般沒有變化的應(yīng)用程序,在設(shè)計時就固定一顆樹。當然,在設(shè)計時固定了樹的結(jié)構(gòu),以后要想修改、增加、刪除樹的節(jié)點,就必須修改源程序。所有不利于擴展。
第二種方式從XML文件中提取,由于XML本身就是樹形結(jié)構(gòu)的,微軟提供的文檔對象模型DOM 可以方便的讀取、操作和修改 XML 文檔。在.NET中,應(yīng)用System.Xml類可以方便地將XML文件加載到TreeView控件中,微軟的MSDN也提供了實例,此處就不再多說。
第三種方式,樹形結(jié)構(gòu)的數(shù)據(jù),從數(shù)據(jù)庫中獲得。一般來講,我們的應(yīng)用程序多數(shù)是基于數(shù)據(jù)庫的。采用這種方式,增加、修改、刪除一顆樹的節(jié)點很方便,只要操作數(shù)據(jù)庫中的數(shù)據(jù)就可以了。而且,這種方式可以和數(shù)據(jù)庫中的其它表做關(guān)聯(lián)、查詢和匯總,通過設(shè)計視圖或存儲過程,很容易查詢出你想要的相關(guān)數(shù)據(jù)。下面,我們主要討論這種方式的設(shè)計和實現(xiàn)。

 

數(shù)據(jù)庫設(shè)計
首先,我們在SQL SERVER 2000里建立一個表tbTree,表的結(jié)構(gòu)設(shè)計如下:

列名 數(shù)據(jù)類型 描述 長度 主鍵
ID Int 節(jié)點編號 4 是
ConText Nvarchar 我們要顯示的節(jié)點內(nèi)容 50
ParentID Int 父節(jié)點編號 4
Depth Int 深度 4

關(guān)于Depth(深度)字段,主要是存放節(jié)點的層數(shù),也就是說這個節(jié)點在樹中的哪個層。
有Depth(深度)字段,我們編程時會比較方便,在SQL查詢時只有加一個where 條件就可以查詢出當前深度的層的所有節(jié)點。
如果我們不設(shè)計Depth(深度)字段,同樣可以做類似的查詢,這就需要在后臺的SQL 查詢中用循環(huán)處理;蛘,你可以不在后臺數(shù)據(jù)庫服務(wù)器端處理,把這些處理放在前臺。下面我們將介紹這幾種處理方式:

在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 ,
[depth] [int] NULL
) ON [PRIMARY]

在表中添加如下記錄:

點擊打開新窗口

 

insert tbtree (ID,Context,ParentID,depth) values ( 1,'中國',0,0)
insert tbtree (ID,Context,ParentID,depth) values ( 2,'北京',1,1)
insert tbtree (ID,Context,ParentID,depth) values ( 3,'天津',1,1)
insert tbtree (ID,Context,ParentID,depth) values ( 4,'河北省',1,1)
insert tbtree (ID,Context,ParentID,depth) values ( 5,'廣東省',1,1)
insert tbtree (ID,Context,ParentID,depth) values ( 6,'廣州',5,2)
insert tbtree (ID,Context,ParentID,depth) values ( 7,'四川省',1,1)
insert tbtree (ID,Context,ParentID,depth) values ( 8,'成都',7,2)
insert tbtree (ID,Context,ParentID,depth) values ( 9,'深圳',5,2)
insert tbtree (ID,Context,ParentID,depth) values ( 10,'石家莊',4,2)
insert tbtree (ID,Context,ParentID,depth) values ( 11,'遼寧省',1,1)
insert tbtree (ID,Context,ParentID,depth) values ( 12,'大連',11,2)
insert tbtree (ID,Context,ParentID,depth) values ( 13,'上海',1,1)
insert tbtree (ID,Context,ParentID,depth) values ( 14,'天河軟件園',6,3)
insert tbtree (ID,Context,ParentID,depth) values ( 15,'汕頭',5,2)

有Depth(深度)字段時在VB6 中的實現(xiàn) :
  我們看一下,用ADD方法添加一個新節(jié)點到TreeView的節(jié)點集合,語法如下:
  Nodes.Add(relative,[relationship][,key][,text][,image][,selectedimage])
從上面的語法,可以看出添加一個節(jié)點,只需要知道父節(jié)點編號的key,就可以通過這個key添加子節(jié)點。
如果數(shù)據(jù)庫中查詢出來的結(jié)果集中是按Depth (深度)列排序的話,就可以先加第一層的節(jié)點、再加第二層的節(jié)點….一直到第N層。所以,下文我寫了一個AddTree函數(shù),參數(shù)是層數(shù)(深度),RS是打開小于等于此層數(shù)的所有記錄,并按層數(shù)排序。所以一層一層地添加,通過循環(huán)記錄集,就可以完成一顆樹。夠簡單吧!

Dim CN As ADODB.Connection '定義數(shù)據(jù)庫的連接
Dim Rs As ADODB.Recordset

'工程--->引用--->Microsoft ActiveX Data Object 2.x(版本號)
Private Sub Form_Load()
Set CN = New ADODB.Connection
‘連接數(shù)據(jù)庫
CN.ConnectionString =
"Provider=sqloledb;Data Source=pmserver;Initial Catalog=Benchmark;User Id=sa;Password=sa;"
CN.Open
End Sub

Private Sub AddTree(ByVal intDepth As Integer)
‘打開記錄集,得到深度小于些深度的所有節(jié)點,并按深度排序
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
‘加入根結(jié)點
Set Xnod = TreeView1.Nodes.Add(, , "key" & Rs.Fields("id"), Rs.Fields("context"))
Else
‘加入子節(jié)點
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

程序運行結(jié)果如下圖所示:

 

點擊打開新窗口

 

沒有Depth(深度)時的實現(xiàn)
上面的程序完全是依靠Depth這一列,如果沒有深度這一列來排序,可以看出,上面的代碼就會出錯!
從tbTree表的設(shè)計可以看出,如果沒有Depth這一列,只要有ID字段和ParentID字段就可以查詢到一個節(jié)點下的所有節(jié)點,答案是肯定的!看我們下面這個存儲過程,其作用就是你只要傳一個ID號,就可以找出下面的所有節(jié)點!而且這些節(jié)點是按層次排序的!

建立存儲過程:

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語句就是一層一層地將地將樹的節(jié)點插入到目的表@tmp中。有興趣的讀者可以自行跟蹤一下。

 

點擊打開新窗口

 

我們利用上面這個存儲過程,可以很容易地用VB6寫出添加樹狀結(jié)構(gòu)的代碼,因為這個存儲過程得到的數(shù)據(jù)是已經(jīng)按層次排好序的,我們只要循環(huán)記錄集,順序添加節(jié)點就可以。

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中實現(xiàn)
在.NET中,由于TreeView控件的用法和VB6中的用法是不一樣的!以前的VB6程序員會因為節(jié)點沒有Key屬性而煩惱!在.NET中,TreeView樹的節(jié)點是一個集合,每個 TreeNode 都可以包含其他 TreeNode 對象的集合。要確定您在樹結(jié)構(gòu)中的位置,得使用 FullPath 屬性。
我們知道,添加節(jié)點只能是在找到節(jié)點之后再此節(jié)點下添加,F(xiàn)在VB.NET少了Key屬性,對操作是一個很大的不便。微軟MSDN有一篇文章用繼承和重載的方法,擴展了TreeView控件,給節(jié)點加了一個key屬性。有興趣的讀者可以看一下HOW TO:Create a Key Property for a TreeView Node in Visual Basic .NET這篇文章。但是美中不足的是:這篇文章只是為TreeNode加了一個NodeKey屬性,但是沒有提供好的Key值檢索功能。盡管這一切我們都可以用代碼來擴展,但是代碼冗長。
所以,添加許多層節(jié)點的樹形結(jié)構(gòu),只能是遞歸調(diào)用。而且,我們下面的代碼很精煉,只需要傳給遞歸過程一個ParentID,就會將這個編號下的所有節(jié)點加載到樹形結(jié)構(gòu)中!
充分體現(xiàn)了:簡單就是好的思想。

設(shè)計思想:從數(shù)據(jù)庫中查詢到所有節(jié)點的記錄,添加到DataView中,利用DataView的.RowFilter屬性得到某個父節(jié)點編號ParentID下的所有記錄,依次遞歸循環(huán)。

在VB.net中實現(xiàn):

Private ds As New DataSet ()
' AddTree遞歸函數(shù)每次都要用到數(shù)據(jù)集中的一個表,所以定義成private
Private Sub Form1_Load
(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' '定義數(shù)據(jù)庫連接
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()
'添加命令,從數(shù)據(jù)庫中得到數(shù)據(jù)
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
'關(guān)閉連接
CN.Close()
End Try
'調(diào)用遞歸函數(shù),完成樹形結(jié)構(gòu)的生成
AddTree(0, Nothing)
End Sub

'̀遞歸添加樹的節(jié)點
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,得到當前的所有子節(jié)點
dvTree.RowFilter = "PARENTID = " + ParentID.ToString

Dim Row As DataRowView
For Each Row In dvTree
If pNode Is Nothing Then '判斷是否根節(jié)點
'̀添加根節(jié)點
Node = TreeView1.Nodes.Add(Row("context").ToString())
'̀再次遞歸
AddTree(Int32.Parse(Row("ID").ToString()), Node)
Else
‘添加當前節(jié)點的子節(jié)點
Node = pNode.Nodes.Add(Row("context").ToString())
'̀再次遞歸
AddTree(Int32.Parse(Row("ID").ToString()), Node)
End If
Node.EnsureVisible()
Next
End Sub

程序運行結(jié)果如下圖所示:

 

點擊打開新窗口

 

在C# 中實現(xiàn):
有了在VB.NET中實現(xiàn)的代碼,我們只要改成C#的語法就可以了:

DataSet ds=new DataSet();
private void Form1_Load(object sender, System.EventArgs e)
{
// 定義數(shù)據(jù)庫連接
SqlConnection CN = new SqlConnection();
try
{
//初始化連接字符串
CN.ConnectionString=
"data source=pmserver;initial catalog=Benchmark;
persist security info=False;user id=sa;Password=sa;";
CN.Open();
//添加命令,從數(shù)據(jù)庫中得到數(shù)據(jù)
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();
}
//調(diào)用遞歸函數(shù),完成樹形結(jié)構(gòu)的生成
AddTree(0, (TreeNode)null);
}

// 遞歸添加樹的節(jié)點
public void AddTree(int ParentID,TreeNode pNode)
{
DataView dvTree = new DataView(ds.Tables[0]);
//過濾ParentID,得到當前的所有子節(jié)點
dvTree.RowFilter = "[PARENTID] = " + ParentID;
foreach(DataRowView Row in dvTree)
{
if(pNode == null)
{ //'̀添加根節(jié)點
TreeNode Node = treeView1.Nodes.Add(Row["ConText"].ToString());
AddTree(Int32.Parse(Row["ID"].ToString()),Node); //再次遞歸
}
else
{ //添加當前節(jié)點的子節(jié)點
TreeNode Node = pNode.Nodes.Add(Row["ConText"].ToString());
AddTree(Int32.Parse(Row["ID"].ToString()),Node); //再次遞歸
}
}
}

(責任編輯:admin)

頂一下
(2)
100%
踩一下
(0)
0%
發(fā)表評論
請自覺遵守互聯(lián)網(wǎng)相關(guān)的政策法規(guī),嚴禁發(fā)布色情、暴力、反動的言論。
評價: