ID OrderNum DeepNum Category
8 1 1 类一
9 2 2 类二
10 3 2 类三
11 4 3 类四
12 5 2 类五
13 6 1 类六
14 7 2 类七
15 8 3 类八……
我想按照DeepNum的顺序缩进绑定TreeView,类一到类八按照OrderNum的顺序排序。其中OrderNum和DeepNum的值可以无限扩展。显示的结果如下:+:类一
|____:类二
|____:类三
|____|____:类四
|____:类五
+:类六
|____:类七
|____|____:类八 ……高难度问题,如果谁帮我解决了我单独奖励200分给他。UP有分。
8 1 1 类一
9 2 2 类二
10 3 2 类三
11 4 3 类四
12 5 2 类五
13 6 1 类六
14 7 2 类七
15 8 3 类八……
我想按照DeepNum的顺序缩进绑定TreeView,类一到类八按照OrderNum的顺序排序。其中OrderNum和DeepNum的值可以无限扩展。显示的结果如下:+:类一
|____:类二
|____:类三
|____|____:类四
|____:类五
+:类六
|____:类七
|____|____:类八 ……高难度问题,如果谁帮我解决了我单独奖励200分给他。UP有分。
解决方案 »
- Repeater里面的textbox怎样判断不能为空
- StringBuilder中的capacity和maxCapacity的意思什么???
- 采用DATAGRID分页功能后,我从DROPDOWNLIST中选第10页,我又在DROPDOWNLIST选另一个部门,可能没有10页吧,连一页也显不出来呢??
- 令人烦恼TreeView控件(高分探讨)
- 急等,网站下载的问题
- 刚转C#,我的代码哪里错了?
- 关于两个页面之间的通信问题
- 数据库中表的插入的相关问题,高手指教!!!!!!!!!!!!!!!!!!
- 急,各位大侠请帮忙:XmlDocument.load(URL)时出现错误
- 引用类的方法出错:未将对象引用设置到对象的实例
- 100分求一正则表达式
- 请问关于不同页面间调用方法使页面刷新的问题,100分请教
关键字 树、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 ONinsert 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=Bench;User Id=sa;Password=sa;" CN.OpenCall 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.CloseEnd Sub
程序运行结果如下图所示:
上面的程序完全是依靠Depth这一列,如果没有深度这一列来排序,可以看出,上面的代码就会出错!
从tbTree表的设计可以看出,如果没有Depth这一列,只要有ID字段和ParentID字段就可以查询到一个节点下的所有节点,答案是肯定的!看我们下面这个存储过程,其作用就是你只要传一个ID号,就可以找出下面的所有节点!而且这些节点是按层次排序的!
建立存储过程:
CREATE PROCEDURE spGetTree ( @ID int)asset nocount ondeclare @tmp table (Id int,ConText varchar(50),ParentID int,depth int)insert @tmp select * from tbtree where ID=@IDwhile 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 @tmpset nocount offGO 剖析:上面的存储过程,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.CloseEnd 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=Bench;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=Bench;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=311318http://support.microsoft.com/default.aspx?kbid=308063http://support.microsoft.com/default.aspx?kbid=317597http://support.microsoft.com/default.aspx?kbid=244954
ID OrderNum DeepNum Category
8 1 1 类一
9 2 2 类二
10 3 2 类三
11 4 3 类四
12 5 2 类五
13 6 1 类六
14 7 2 类七
15 8 3//如果把这里改为10怎么办: 类八
表改为这样要好一些
ID OrderNum 父类的id Category
8 1 0 类一
9 2 8 类二
10 3 8 类三
11 4 11 类四
12 5 8 类五
13 6 0 类六
14 7 13 类七
15 8 14 类八
上面的程序完全是依靠Depth这一列,如果没有深度这一列来排序,可以看出,上面的代码就会出错!
从tbTree表的设计可以看出,如果没有Depth这一列,只要有ID字段和ParentID字段就可以查询到一个节点下的所有节点,答案是肯定的!看我们下面这个存储过程,其作用就是你只要传一个ID号,就可以找出下面的所有节点!而且这些节点是按层次排序的!
建立存储过程:
CREATE PROCEDURE spGetTree ( @ID int)asset nocount ondeclare @tmp table (Id int,ConText varchar(50),ParentID int,depth int)insert @tmp select * from tbtree where ID=@IDwhile 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 @tmpset nocount offGO 剖析:上面的存储过程,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.CloseEnd 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=Bench;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=Bench;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=311318http://support.microsoft.com/default.aspx?kbid=308063http://support.microsoft.com/default.aspx?kbid=317597http://support.microsoft.com/default.aspx?kbid=244954
上面的程序完全是依靠Depth这一列,如果没有深度这一列来排序,可以看出,上面的代码就会出错!
从tbTree表的设计可以看出,如果没有Depth这一列,只要有ID字段和ParentID字段就可以查询到一个节点下的所有节点,答案是肯定的!看我们下面这个存储过程,其作用就是你只要传一个ID号,就可以找出下面的所有节点!而且这些节点是按层次排序的!
建立存储过程:
CREATE PROCEDURE spGetTree ( @ID int)asset nocount ondeclare @tmp table (Id int,ConText varchar(50),ParentID int,depth int)insert @tmp select * from tbtree where ID=@IDwhile 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 @tmpset nocount offGO 剖析:上面的存储过程,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.CloseEnd 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=Bench;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=Bench;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=311318http://support.microsoft.com/default.aspx?kbid=308063http://support.microsoft.com/default.aspx?kbid=317597http://support.microsoft.com/default.aspx?kbid=244954
{
DataView dv=new DataView();
TreeNode tmpNd;
string intId;
dv.Table=dset.Tables["tree"];
dv.RowFilter="ParentId='" + parentId + "'" ;
foreach(DataRowView drv in dv)
{
if (objnode=="1")
{
this.groupid.Text=drv["NodeId"].ToString();
objnode="2";
}
tmpNd=new TreeNode();
tmpNd.ID=drv["NodeId"].ToString();
tmpNd.Text=drv["NodeName"].ToString();
tmpNd.ImageUrl="img/"+drv["Icon"].ToString();
tmpNd.Expanded=true;
Nds.Add(tmpNd);
intId=drv["ParentId"].ToString();
InitTree(tmpNd.Nodes,tmpNd.ID);
}
}
==============================================================
Imports System.Collections.Specialized
Imports Microsoft.Web.UI.WebControls
Public Class TestTreeView
Inherits System.Web.UI.Page#Region " Web 窗体设计器生成的代码 "
'''该处代码省略
#End Region Dim arr As ArrayList
Dim Pnd As TreeNodeCollection
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
arr = New ArrayList arr.Add(New Item(1, " 类一"))
arr.Add(New Item(2, "类二")) arr.Add(New Item(2, "类三"))
arr.Add(New Item(3, "类四"))
arr.Add(New Item(2, "类五"))
arr.Add(New Item(1, "类六"))
arr.Add(New Item(2, "类七"))
arr.Add(New Item(3, "类八"))
Dim i As Integer
Pnd = TreeView1.Nodes
For i = 0 To arr.Count - 1
AddNode(i, arr(i))
Next End Sub
Private Sub AddNode(ByVal index As Integer, ByVal Itm As Item)
''todo 如果插入节点深度>现有深度+1,那么直接退出
Dim nd As New TreeNode
nd.ID = index
nd.NodeData = Itm._level
nd.Text = Itm._text
If Itm._level = 1 Then ''如果是顶层节点
TreeView1.Nodes.Add(nd)
Pnd = nd.Nodes
Exit Sub
End If
If Itm._level <= CType(Pnd.Parent, TreeNode).NodeData Then
Dim i As Integer
For i = 0 To CType(Pnd.Parent, TreeNode).NodeData - Itm._level
Pnd = CType(CType(Pnd.Parent, TreeNode).Parent, TreeNode).Nodes
Next
End If
Pnd.Add(nd)
Pnd = nd.Nodes
End Sub
End Class'''用于记录节点的深度和文本Public Class Item
Public _level As Integer
Public _text As String Public Sub New(ByVal ID As Integer, ByVal Text As String)
_level = ID
_text = Text
End SubEnd Class
该组件界面超简单,http://www.flash8.net/bbs/UploadFile/2005-3/200539215613418.jpg
所有对类的操作都浓缩在这张图里,但要实现起来却是困难重重。
加一个ParentID。递归就可以了。
{
Db.Category myCategory = new Category();
SqlDataReader dr1 = myCategory.drMaxOrderNum();
if(dr1.Read())
{
maxOrderNum = dr1.GetInt32(0);//取得最大OrderNum
}
else
{
maxOrderNum = 0;
} //Response.Write(maxOrderNum);
SqlDataReader dr2 = myCategory.drMaxDeepNum();
if(dr2.Read())
{
maxDeepNum = dr2.GetInt32(0);//取得最小DeepNum
}
else
{
maxDeepNum = 0;
} //Response.Write(maxDeepNum); DataSet ds = myCategory.dsSelectOrderBy(); int i;
int r; for(r=0;r<maxOrderNum;r++)
{
for(i=0;i<maxDeepNum;i++)
{
if(i<maxDeepNum)
{
ds.Tables[0].Rows[i][2] = "0" + ds.Tables[0].Rows[i][2];
}
else
{
ds.Tables[0].Rows[i][2] = ds.Tables[0].Rows[i][2];
}
TreeNode myNode = new TreeNode();
myNode.Text = ds.Tables[0].Rows[i][3].ToString();
myNode.Expanded = true;
tvCategory.Nodes.Add(myNode);
}
}
}
for(int i=0;i<ds.Tables[0].Rows.Count;i++)
{
if(int.Parse(ds.Tables[0].Rows[i][2].ToString())==1)
{
ds.Tables[0].Rows[i][3] = ds.Tables[0].Rows[i][3].ToString();
}
else
{
for(int j=1;j<int.Parse(ds.Tables[0].Rows[i][2].ToString());j++)
{
ds.Tables[0].Rows[i][3] = "|-" + ds.Tables[0].Rows[i][3].ToString();
}
}
}
ddlCategory.DataSource = ds.Tables[0].DefaultView;
ddlCategory.DataTextField = ds.Tables[0].Columns[3].ToString();
ddlCategory.DataValueField = ds.Tables[0].Columns[3].ToString();
ddlCategory.DataBind();
While(栈顶DeepNum>=当前DeepNum)出栈;
记录加到栈顶记录下
当前DeepNum/Key进栈
直到所有记录完成这样就可以了,并不是很复杂啊
用来保存某个深度的最后一个节点
对于下一个节点,总是其深度值-1的对应lastnode的子节点
建议你从DB将所有数据搜索出来后,再在数据集里面处理递归
drop table [dbo].[Board]
GO
CREATE TABLE [dbo].[Board] (
[BoardId] [int] IDENTITY (1, 1) NOT NULL ,
[PareId] [int] NOT NULL ,
[BoardName] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
[BoardMark] [varchar] (255) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
得到树的深度存储过程:
CREATE PROCEDURE dbo.SpBoardTreeDepth
AS
declare @level int
declare @t table(boardid int,pareid int,boardname varchar(50),level int)
set @level = 1
insert into @t select boardid,pareid,boardname,@level from board where pareid = 0
while(@@rowcount>0)
begin
set @level=@level+1
insert into @t
select a.boardid,a.pareid,a.boardname,@level from board a
join @t b on a.pareid = b.boardid where b.level=@level-1
end
select boardid,pareid,boardname,level from @t
RETURN
GO 绑定到TreeView控件:
private System.Windows.Forms.TreeView treeView1;
public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
//
DataTable dt = Popo.Dal.SqlHelp.LoadData("SpBoardTreeDepth",null).Tables[0];
string rootId = "0";
CreateChild(treeView1.Nodes,rootId,dt);
}
void CreateChild(TreeNodeCollection nodeCollection,string id ,DataTable dt){
if(dt.Rows.Count == 0)
return;
for(int i=0;i<dt.Rows.Count;i++){
DataRow dr = dt.Rows[i];
if(dr["pareid"].ToString() == id){
string deep = "" ;
for(int j=0;j<System.Convert.ToInt32(dr["level"].ToString())-1;j++){
deep +=" ";
}
deep += "┣ ";
TreeNode node = new TreeNode(dr["boardname"].ToString());
node.Tag = dr["boardid"].ToString();
nodeCollection.Add(node);
string bId = dr["boardid"].ToString();
dt.Rows.Remove(dt.Rows[i]);
CreateChild(node.Nodes,bId,dt);
CreateChild(nodeCollection,id,dt);
}
}
}