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     {//取得要移动的节点,并记录下,注意,这里不能直接Remove,否则取不到下一个节点。
      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@侠缘      
 (~)@(~)  一辈子,用心做一件事!
--------------------------------
  学而不思则罔,思而不学则怠!  
================================
原文地址:https://www.cnblogs.com/WuCountry/p/300151.html