TreeView的几个使用小技
最近的项目里要用到TreeView控件,而它的数据来源是从数据库里读取,而不是磁盘。我试过从从磁盘读取目录结构,感觉还不错,曾经在5秒内返回了我的C盘所有数据。
然而从数据库里读取数据是一个很头痛的事情,因为不得不用递归来处理数据,而这样是很浪费时间的,特别是数据库的读取上,基本上90%的时间都消费在数据库的读取上。下面是这样的一个例子:
private void CreateTreeNode(TreeNode i_node, long i_rootFolderID)
{
Folder m_tempFolder = new Folder();
DataTable m_table = new DataTable();
WaveFolderManager.GetSubFolderData(m_table,i_rootFolderID);
foreach(DataRow m_row in m_table.Rows)
{
TreeNode m_subFolder = new TreeNode();
m_subFolder.ExpandedImageUrl = m_openFolderPic;
m_subFolder.ImageUrl = m_closeFolderPic;
m_subFolder.Text = m_row["f_folderName"].ToString();
m_subFolder.NodeData += m_row["f_id"].ToString()+"|";
m_subFolder.NodeData += m_row["f_parentID"].ToString()+"|";
m_subFolder.NodeData += m_row["f_clientID"].ToString()+"|Folder";
CreateTreeNode(m_subFolder,Convert.ToInt64(m_row["f_id"]));
i_node.Nodes.Add(m_subFolder);
}
m_table.Clear();
WaveFolderManager.GetFiles(m_table,i_rootFolderID);
foreach(DataRow m_row in m_table.Rows)
{
TreeNode m_files = new TreeNode();
m_files.CheckBox = true;
m_files.Text = m_row["v_fileName"].ToString();
m_files.NodeData += m_row["v_id"].ToString()+"|";
m_files.NodeData += m_row["v_folderID"].ToString()+"|";
m_files.NodeData += m_row["v_clientID"].ToString()+"|File";
i_node.Nodes.Add(m_files);
}
}
上面的算法在数据库数据不大,而且数据库处理速度很快的时候可以临时用一下吧,但真的太不理想了。10级以内的调用都要花5秒左右的时间,而且总记录数不到100,真是郁闷死了。
后来改用每一级展开,也就是让TreeView的AutoPostBack为真,并处理OnExpend事件,这样就可以每展开一级的时候来载入数据。这是一个很不错的选择,同样的算法,只是只读取到下一及,并用Loading来代替子树。
private void CreateSubTreeNode(TreeNode i_node,long i_rootFolderID)
{
Folder m_tempFolder = new Folder();
DataTable m_table = new DataTable();
WaveFolderManager.GetSubFolderData(m_table,i_rootFolderID);
foreach(DataRow m_row in m_table.Rows)
{
TreeNode m_subFolder = new TreeNode();
m_subFolder.ExpandedImageUrl = m_openFolderPic;
m_subFolder.ImageUrl = m_closeFolderPic;
m_subFolder.Text = ""+m_row["f_folderName"].ToString()+"";
m_subFolder.NodeData += m_row["f_id"].ToString()+"|";
m_subFolder.NodeData += m_row["f_parentID"].ToString()+"|";
m_subFolder.NodeData += m_row["f_clientID"].ToString()+"|Folder";
// CreateTreeNode(m_subFolder,Convert.ToInt64(m_row["f_id"]));
long m_subID = Convert.ToInt64(m_row["f_id"]);
if(WaveFolderManager.GetSubFolderNumber(m_subID)>0||WaveFolderManager.GetSubFilesNumber(m_subID)>0)
{
TreeNode m_subFolders = new TreeNode();
m_subFolders.Text = "Click 'Load SubFolder' to Load data...";
m_subFolder.Nodes.Add(m_subFolders);
}
i_node.Nodes.Add(m_subFolder);
}
m_table.Clear();
WaveFolderManager.GetFiles(m_table,i_rootFolderID);
foreach(DataRow m_row in m_table.Rows)
{
TreeNode m_files = new TreeNode();
m_files.CheckBox = true;
m_files.Text = m_row["v_fileName"].ToString();
m_files.NodeData += m_row["v_id"].ToString()+"|";
m_files.NodeData += m_row["v_folderID"].ToString()+"|";
m_files.NodeData += m_row["v_clientID"].ToString()+"|File";
i_node.Nodes.Add(m_files);
}
}
这样确实不错,每次读取数据库的时间都只有0.2秒左右。而这样最不好的就是每选择一个节点都会有一次的载入,而且TreeView控件的事件是一起的,不能独立出展开事件及点击事件,而树载入后,其它的主要操作就是选择节点,而每次都载入一次让人很受不了。虽然基本上不花什么时间,但如果在INTERT上,大量的时间可能要消费在来回的路费上。所以该方法也放弃了。。。。。。。
最可行的方法可能就是在页面内存里进行数据处理,也就是先一口气从数据库里取出所有的记录,然后在页面逻辑里生成树,这样方法很有效,而且页面还可以缓存数据。这是它的算法:
public void ReloadFolderAndFileData(DataSet i_dataSet)
{
DataTable m_table1 = new DataTable("m_folders");
DataTable m_table2 = new DataTable("m_files");
WaveFolderManager.GetAllFolders(m_table1);
WaveFileManager.GetAllFiles(m_table2);
i_dataSet.Tables.Add(m_table1);
i_dataSet.Tables.Add(m_table2);
}
private void CreateTreeNodeWithCache(TreeNode i_node, long i_rootFolderID,DataSet i_dataset)
{
foreach(DataRow m_row in i_dataset.Tables["m_folders"].Rows)
{
if(Convert.ToInt64(m_row["f_parentID"])==i_rootFolderID)
{
TreeNode m_subFolder = new TreeNode();
m_subFolder.ExpandedImageUrl = m_openFolderPic;
m_subFolder.ImageUrl = m_closeFolderPic;
m_subFolder.Text = m_row["f_folderName"].ToString();
m_subFolder.NodeData += m_row["f_id"].ToString()+"|";
m_subFolder.NodeData += m_row["f_parentID"].ToString()+"|";
m_subFolder.NodeData += m_row["f_clientID"].ToString()+"|Folder";
CreateTreeNodeWithCache(m_subFolder,Convert.ToInt64(m_row["f_id"]),i_dataset);
i_node.Nodes.Add(m_subFolder);
}
}
foreach(DataRow m_row in i_dataset.Tables["m_files"].Rows)
{
if(Convert.ToInt64(m_row["v_folderID"])==i_rootFolderID)
{
TreeNode m_files = new TreeNode();
m_files.CheckBox = true;
m_files.Text = m_row["v_fileName"].ToString();
m_files.NodeData += m_row["v_id"].ToString()+"|";
m_files.NodeData += m_row["v_folderID"].ToString()+"|";
m_files.NodeData += m_row["v_clientID"].ToString()+"|File";
i_node.Nodes.Add(m_files);
}
}
}
然而这给树的操作带来了很多的不便。首先,当用户删除一个结点的时候,数据库是一定要更新的。而这个时候还得更新内存里的DataSet并且还要更新TreeView结构,这样一来,三者同时更新的算法可不好受。我没有继续下去,只是做一个更改节点名字的小函数,而删除,转移和复制就都放弃了,因为实在是不好处理好三者的同步(而且学不知道这样做的速度到底怎样)。最头痛的还是TreeView的Node不能在循环里更改,这应该是.net的属性,也就是当用foreach在一个集合里循环的时候,是不能修改这个集合的,所以在TreeNodes里循环处理数据的时候,不能添加和删除节点,这是很头痛的,这不得不再用一个集合记录下哪些结点要添加,哪些结点要删除。而这样又添加了一个表在内存里(小做了一下,没完成)。所以,最后也放弃了这个方案。
最后,得用TreeView自己的数据结构来处理数据。因为从数据库里读取的记录,最后要的是树型的数据结构,而不是记录本身,而TreeView已经在第一次载入的时候形成了树,所以就没有必要再载入了。而TreeView有视图缓存在客户端,这样本身就保存了数据,利用这样一个特点,可以考虑这样的方案:只处理数据库与TreeView本身,而且充分利用视图缓存。
以下是几个主要的函数:
if(!IsPostBack)
{//只在第一次访问的时候读取数据库。
this.LoadTreeNodeData();
}
//还是从内存的DataSet里生成树,但DataSet并不缓存在页面里。
private void CreateTreeNodeWithCache(TreeNode i_node, long i_rootFolderID,DataSet i_dataset)
{
foreach(DataRow m_row in i_dataset.Tables["m_folders"].Rows)
{
if(Convert.ToInt64(m_row["f_parentID"])==i_rootFolderID)
{
TreeNode m_subFolder = new TreeNode();
m_subFolder.ExpandedImageUrl = m_openFolderPic;
m_subFolder.ImageUrl = m_closeFolderPic;
m_subFolder.Text = m_row["f_folderName"].ToString();
m_subFolder.NodeData += m_row["f_id"].ToString()+"|";
m_subFolder.NodeData += m_row["f_parentID"].ToString()+"|";
m_subFolder.NodeData += m_row["f_clientID"].ToString()+"|Folder";
CreateTreeNodeWithCache(m_subFolder,Convert.ToInt64(m_row["f_id"]),i_dataset);
i_node.Nodes.Add(m_subFolder);
}
}
foreach(DataRow m_row in i_dataset.Tables["m_files"].Rows)
{
if(Convert.ToInt64(m_row["v_folderID"])==i_rootFolderID)
{
TreeNode m_files = new TreeNode();
m_files.CheckBox = true;
m_files.Text = m_row["v_fileName"].ToString();
m_files.NodeData += m_row["v_id"].ToString()+"|";
m_files.NodeData += m_row["v_folderID"].ToString()+"|";
m_files.NodeData += m_row["v_clientID"].ToString()+"|File";
i_node.Nodes.Add(m_files);
}
}
}
这里是更新树的时候最要注意的:
private void Toolbar1_ButtonClick(object sender, System.EventArgs e)
{
ToolbarButton m_button = sender as ToolbarButton;
string m_commandID = m_button.ID;
switch(m_commandID)
{
case "c_command1"://Rename a file
RenameFile();
break;
case "c_command2"://Delete a file
ShowCaution();
break;
case "c_command3"://Move files
string[] m_nodeData1 = GetNodeData();
if(m_nodeData1.Length<4||m_nodeData1[3].ToLower()!="folder")return;
DataTable m_table = new DataTable();
m_table.Columns.Add(new DataColumn("v_id"));
m_table.Columns.Add(new DataColumn("v_folderID"));
m_table.Columns.Add(new DataColumn("v_clientID"));
m_table.Columns.Add(new DataColumn("v_fileName"));
m_table.Columns.Add(new DataColumn("v_nodeIndex"));
MoveFiles(TreeView1.GetNodeFromIndex("0"),GetSelectNodeID(),m_table);
TreeNode m_node = TreeView1.GetNodeFromIndex(TreeView1.SelectedNodeIndex);
TreeNode[] m_tempNode = new TreeNode[m_table.Rows.Count];
for(int i=0;i
m_tempNode[i] = TreeView1.GetNodeFromIndex(m_table.Rows[i]["v_nodeIndex"].ToString());
}
foreach(TreeNode i_node in m_tempNode)
{//一口气删除
i_node.Remove();
}
foreach(DataRow m_row in m_table.Rows)
{
TreeNode m_subFolder = new TreeNode();
m_subFolder.CheckBox = true;
m_subFolder.Text = m_row["v_fileName"].ToString();
m_subFolder.NodeData += m_row["v_id"].ToString()+"|";
m_subFolder.NodeData += m_row["v_folderID"].ToString()+"|";
m_subFolder.NodeData += m_row["v_clientID"].ToString()+"|file";
m_node.Nodes.Add(m_subFolder);
}
// this.TreeView1.Nodes.Clear();原来要重新载入树,而修改后,不用再载入了,而是直接修改视图里的树
// LoadTreeNodeData();
break;
case "c_command4"://Copy files
string[] m_nodeData2 = GetNodeData();
if(m_nodeData2.Length<4||m_nodeData2[3].ToLower()!="folder")return;
DataTable m_table2 = new DataTable();
m_table2.Columns.Add(new DataColumn("v_id"));
m_table2.Columns.Add(new DataColumn("v_folderID"));
m_table2.Columns.Add(new DataColumn("v_clientID"));
m_table2.Columns.Add(new DataColumn("v_fileName"));
CopyFiles(TreeView1.GetNodeFromIndex("0"),GetSelectNodeID(),m_table2);
TreeNode m_node2 = TreeView1.GetNodeFromIndex(TreeView1.SelectedNodeIndex);
foreach(DataRow m_row in m_table2.Rows)
{
TreeNode m_subFolder = new TreeNode();
m_subFolder.CheckBox = true;
m_subFolder.Text = m_row["v_fileName"].ToString();
m_subFolder.NodeData += m_row["v_id"].ToString()+"|";
m_subFolder.NodeData += m_row["v_folderID"].ToString()+"|";
m_subFolder.NodeData += m_row["v_clientID"].ToString()+"|file";
m_node2.Nodes.Add(m_subFolder);
}
break;
case "c_command5":
ShowFileInfo();
break;
case "c_command6":
DownloadFile();
break;
default:
break;
}
}
///下面是一个MoveFile的函数:
///
///
///
///
///
private void MoveFiles(TreeNode i_node,long i_folderID,DataTable i_table)
{
foreach(TreeNode m_node in i_node.Nodes)
{
if(m_node.Checked)
{
MoveFile(m_node,i_folderID);
string[] m_nodeData = m_node.NodeData.Split(new char[]{'|'});
DataRow m_row = i_table.NewRow();
m_row["v_id"] = m_nodeData[0];
m_row["v_folderID"] = i_folderID;
m_row["v_clientID"] = m_nodeData[2];
m_row["v_fileName"] = m_node.Text;
m_row["v_nodeIndex"]= m_node.GetNodeIndex();
i_table.Rows.Add(m_row);
}
MoveFiles(m_node,i_folderID,i_table);
}
}
private void MoveFile(TreeNode i_node,long i_folderID)
{
string[] m_nodeData = i_node.NodeData.Split(new char[]{'|'});
long m_fileID = Convert.ToInt64(m_nodeData[0]);
WaveFile m_file = new WaveFile();
m_file.LoadFileData(m_fileID);
m_file.FolderID = i_folderID;
m_file.UpdateData(m_fileID);
m_file.Dispose();
}
/\_/\
(=^o^=) Wu.Country@侠缘
(~)@(~) 一辈子,用心做一件事!
--------------------------------
学而不思则罔,思而不学则怠!
================================
posted on 2005-12-19 14:45 Wu.Country@侠缘 阅读(535) 评论(1) 编辑 收藏 举报