最近写项目需要用到TREEVIEW树控件承载后台数据源.其中碰到一些难题,特别是在树控件对大量数据读取速度的算法编写上碰到很多问题....在网上找了一些关于这方面的资料,跟大家一起分享一下.
Treeview是一个重要的控件,无论是在VB.NET,C#还是Delphi、VC++等各种语言中,都充当了导航器的作用。在实际工作中,很多情况下需要将Treeview与数据库进行连接,以填充其节点。然而,往往由于数据库数据众多,一次性灌入所有的根节点与子节点,会耗用大量的起始等待时间。解决的方案,应该是动态加载节点,起始的时候,只加载根节点,在点击某个根节点时,才加载其子节点,再点击某个子节点时,才加载子节点的子节点,以此类推,这样的处理方法可以达到提高工作效率,节省访问时间的妙用。
本文仅以VB.NET为例,说明此方法的使用。其它的编程语言,可以进行仿效.
首先应该有个数据库,用以保存数据结构与数据。本文需要的数据库为一个Project.mdb。其结构为(为清楚起见,以汉字字段为例,实际应用时,请自行更改):
表名:根节点
字段 类型 大小 主键
根节点编号 文本 10 Y
根节点名称 文本 10
表名:第一级子节点
字段 类型 大小 主键
根节点编号 文本 10 Y
第一级子节点编号 文本 10 Y联合主键
第一级子节点名称 文本 10
表名:第二级子节点
字段 类型 大小 主键
第一级子节点编号 文本 10 Y
第二级子节点编号 文本 10 Y联合主键
第二级子节点名称 文本 10
三个表的关系已经很清楚了,在此不再罗列。表内填充的数据为:
根节点编号为1、2、3这样类推,其名称也就为根节点1、根节点2、根节点3
第一级子节点编号规则为:根节点1之下的子节点编号为11、12、13等以此类推,根节点2之下的子节点编号为21、22……子节点名称为:子节点1、子节点2……
第二级子节点编号规则为:第一级子节点ij之下的第二级子节点的编号为ij1、ij2……,其名称为统一的:孙节点1、孙节点2……
现在让我们简单分析一下treeview的结构(笔者准备在另文专门剖析一下Treeview的结构,以方便有兴趣的网友进行高级应用)
Treeview是由节点TreeNode组成的,第一级的称之为根节点TreeRoot,在根节点之下一级的称之为某个根节点的子节点TreeLeaf,某个子节点之下一级的子节点就称为该子节点的子节点。第个节点有两个标识方式,一个是它的Text,即显示出来的内容;另一个是它的Tag属性,一般用唯一标识码对其进行标识,以用于在使用时对节点的识别。在本文中,也主要用Text属性来显示节点的名称字段,用Tag属性来显示节点的编号属性。(节点编号被设为主键,也就是唯一的标识了)
1.加载根节点
好了,我们该开始在VB.NET中进行演练了!第一步,当然是看看怎么在窗体起始的时候加载根节点:
'定义公用变量 Dim myconnection As New OleDb.OleDbConnection() Dim MyAdapater As New OleDb.OleDbDataAdapter() Dim mycommand As New OleDb.OleDbCommand() Dim ds As New DataSet() Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load '载入根节点表至treeview中,作为第一级 myconnection.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source= " & Application.StartupPath & "/project.mdb" '数据库连接请自行更换 mycommand.CommandText = "SELECT 根节点编号,根节点名称 FROM 根节点" mycommand.Connection = myconnection Try myconnection.Close() myconnection.Open() Dim mysqlreader As OleDb.OleDbDataReader = mycommand.ExecuteReader TreeView1.Nodes.Clear() While mysqlreader.Read() Dim tree_root As New TreeNode() tree_root.Tag = mysqlreader.GetString(0) '把编号放入tag中 tree_root.Text = mysqlreader.GetString(1) '树上显示的是根节点名称 '请根据你数据库字段的类型来决定是否用getstring或其它类型 TreeView1.Nodes.Add(tree_root) End While Catch ex As Exception MessageBox.Show(ex.ToString, "数据表根节点载入错误", vbOKOnly) Finally myconnection.Close() End Try TreeView1.ExpandAll() TreeView1.Select() End Sub
好了,现在运行一下程序,你可以看到如图1所示的起始界面
2.点击时加入子节点
对TreeView的点击,对于TreeView控件本身,并没有为哪一个级别的Node编写点击(选择)事件处理程序,而是把所有节点的点击事件都写入了一个AfterSelect事件中。因此,在编写点击加入子节点的程序之前,我们还必须编写一个查找点击的节点是哪一级节点的方法。
Public Function NodeLevel(ByVal n As TreeNode) As Byte '* 找出树中当前节点的级数 Dim i As Byte = 1 Dim m As String Do Until n.Parent Is Nothing n = n.Parent i += 1 Loop Return i End Function
通过这个函数,就可以很方便地得到节点的级别。现在我们可以放心地编写节点选择事件处理程序,以实现动态加载各级节点的子节点。
Private Sub TreeView1_AfterSelect (ByVal sender As System.Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles TreeView1.AfterSelect Select Case NodeLevel(e.Node).ToString Case "1" If e.Node.GetNodeCount(False) = 0 Then mycommand.CommandText = "select 第一级子节点编号,第一级子节点名称 from 第一级子节点 where 根节点编号 ='" & e.Node.Tag & "'" fill_treeleaf() End If Case "2" If e.Node.GetNodeCount(False) = 0 Then mycommand.CommandText = "select 第二级子节点编号,第二级子节点名称 from 第二级子节点 where 第一级子节点编号 ='" & e.Node.Tag & "'" fill_treeleaf() End If End Select End Sub Public Sub fill_treeleaf() mycommand.Connection = myconnection Try myconnection.Open() Dim mysqlreader As OleDb.OleDbDataReader = mycommand.ExecuteReader While mysqlreader.Read() Dim tree_leaf As New TreeNode() tree_leaf.Tag = mysqlreader.GetString(0) tree_leaf.Text = mysqlreader.GetString(1) TreeView1.SelectedNode.Nodes.Add(tree_leaf) End While Catch ex As Exception MsgBox(ex.Message) Finally myconnection.Close() End Try End Sub本文作者提醒大家:请大家要熟悉Text与Tag的作用,在使用时会达到很好的效果。不知道大家注意没有,本文出自于早些年,作者采用的后台数据源是传统的数据库形式,如果用现在流行的XML格式作为后台数据源呢?效果会怎样呢?....我试过,用XML作为后台数据源更为方便,在运行速率上更好.改天我把代码贴上来与大家一起分享.