[C#]樹狀圖(TreeView)的用法

  • 76070
  • 0

展示一些簡單的樹狀圖用法

這功能摸了很久,
主要是要針對上面交代下來的功能去做客製化,
弄到後面真的有一把鼻涕一把眼淚的感覺...
這邊只是做個紀錄,小弟功力火侯還不到家,
希望路過的各位高手多多指教。

 

在開始之前先說明一下客製化的需求:
1. 元件化
2. 自定義的節點圖示
3. 節點根據資料庫的資料產生
4. 根據網址帶的參數去展開樹狀圖
5. 節點要可以帶連結 

 

在撰寫的過程中,
最麻煩就是要根據資料的主從結構來定義父節點子節點之間的關係,
在我的 CASE 裡面,是這樣定義的:

父 節 點:1.1
子節點1:1.1.1
子節點2:1.1.2
子節點3:1.1.3 

 

不知道各位看出來了沒有,
簡單的說就是用 " . " 來區分上、下層結點,
而父節點 1.1 又隸屬在節點 1 之下, 整體架構如下圖:

在我的資料庫中,
我用一個欄位來記錄這樣的關係:

 

有一點要提醒,就是最上層的根目錄是另外放在一個資料表,暫且統稱資料表ROOT好了,
至於其他節點又會放在另一個資料表,統稱資料表NODE
而在網頁上,我會將這些這需參數放在網址,(例如:test.aspx?id=23)
讓程式在頁面載入可以去抓這些資訊:

01 void Page_Load(object sender, EventArgs e)
02     {
03         if (Request.QueryString["id"] == string.Empty)
04         {
05             Message.Text = "網址參數有誤";
06             TreeView1.Visible = false;
07         }

08         else
09         {
10             queryString = Request.QueryString["id"].Split('.'); //分割網址參數
11             //
12             if (!Page.IsPostBack)
13             {
14                 addService(); //新增父節點
15             }

16         }

17     }

 

 

新增父節點程式:
(資料來源是資料表ROOT)

01 void addService()
02     {
03         string nodeName = string.Empty;        
04         string nodeLink = "";
05         //
06         string QryString = "SELECT * FROM rootDB";
07         //        
08         SqlConn.Open();
09         SqlCmd = new SqlCommand(QryString, SqlConn);
10         DataReader = SqlCmd.ExecuteReader();
11         while (DataReader.Read())
12         {            
13             nodeName = DataReader["sevName"].ToString();            
14             nodeLink = DataReader["sevLink"].ToString();
15         }

16         DataReader.Close();
17         SqlConn.Close();
18         //      
19         if (nodeName == string.Empty || nodeLink == string.Empty)
20         {
21             Message.Text = "找不到服務";
22             TreeView1.Visible = false;
23         }

24         else
25         {
26             if (chkQueryString(Request.QueryString["id"]))
27             {
28                 TreeView1.Nodes.Clear();
29                 //          
30                 TreeNode MyNode = new TreeNode();
31                 MyNode.Expanded = false; //節點是否展開
32                 MyNode.Text = nodeName; //節點文字
33                 MyNode.Value = GetServName(Request.QueryString["id"]); //節點值
34                 MyNode.NavigateUrl = nodeLink; //目標連結
35                 MyNode.Target = "_self"; //連結開啟方式
36                 //  
37                 if (chkChildNodes(MyNode)) //如果有子節點才會有展開標籤
38                 {
39                     MyNode.PopulateOnDemand = true;
40                     doExpand(MyNode); //根據網址參數設定是否預先展開
41                 }

42                 else
43                 {
44                     MyNode.PopulateOnDemand = false;
45                 }

46                 //        
47                 TreeView1.Nodes.Add(MyNode);
48             }

49             else
50             {
51                 Message.Text = "參數錯誤";
52                 TreeView1.Visible = false;
53             }

54         }

55     }

前半段到17行的部分用途在從資料表ROOT中取得服務的名稱,
第二階段從26行開始首先檢查網址的參數是否合法(存在於資料庫中),
若合法,則進行根目錄的初始化(根目錄自己本身也算一個節點喔)以及雜項設定。(28~35行)

 

但是截至上述步驟,也只能確認在資料表ROOT確實有這個服務存在,
還不能確定在該服務之下有沒有其他節點,
因此從37行開始啟動一系列的檢查步驟,
首先將剛剛初始化的根目錄節點MyNode丟到副程式chkChildNodes裡面檢查有沒有其他的子節點,
如果有,就要將MyNode的PopulateOnDemand屬性設定為True,
這個環節相當重要,
因為PopulateOnDemand就是決定樹狀圖是否動態載入節點的關鍵。

 

40行的地方是決定樹狀圖要展開到哪一層。(根據網址參數)
最後,47行的地方,無論有沒有子節點,最後都要把根目錄塞到樹狀圖中。

 

檢查子節點副程式↓:
(資料來源是資料表NODE)

01 bool chkChildNodes(TreeNode theNode)
02     {
03         int Count = 0;
04         bool MyOutput = false;
05         //
06         string chkCmd = "SELECT COUNT(*) FROM nodeDB";
07         SqlConn.Open();
08         SqlCmd = new SqlCommand(chkCmd, SqlConn);
09         Count = (int)SqlCmd.ExecuteScalar();
10         SqlConn.Close();
11         //
12         if (Count == 0)
13         { }
14         else
15         {
16             MyOutput = true;
17         }

18         //
19         return MyOutput;
20     }

 

決定根目錄是否展開的副程式↓:

01 void doExpand(TreeNode _node)
02     {
03         if (Request.QueryString["id"] == GetServName(Request.QueryString["id"]))
04         {
05             _node.Collapse(); //網址參數與根目錄相同→收合
06         }

07         else
08         {
09             _node.Expand();  
10         }

11     }


 

 


以上呢...是DEMO根目錄的產生,
接下來才要開始子節點的製造過程,
至於為啥要這麼麻煩...只能說~老闆最大~....

 

進入這個階段之前呢,
先貼一下樹狀圖的 HTML CODE 好了:

1 <asp:TreeView ID="TreeView1" runat="server"  
2         EnableClientScript="False"
3         NodeWrap="true"  
4         OnTreeNodePopulate="PopulateNode">

5 </asp:TreeView>
6 <asp:Label ID="Message" runat="server" ></asp:Label>

 

為啥要先貼呢?
因為這裡面包含一個很重要的部分,
就是 OnTreeNodePopulate 屬性。
接下來會有點雜...要了解這屬性之前,先回頭看看上面 PopulateOnDemand 的定義,



很麻煩?
好吧...一起解說比較方便,
所謂的 PopulateOnDemand 就是設定樹狀圖是否動態載入節點,
若要動態填入節點,須先將節點的 PopulateOnDemand 屬性設為 true,然後定義 OnTreeNodePopulate 事件的處理方法,以程式的方式填入節點。

 

以上是改寫自 MSDN,
我用一個比較直覺的方式解說,
一般人在開啟檔案總管時,要是看到資料夾旁邊有個 [+] 的圖示,就知道裡面還有東西,
PopulateOnDemand 就是在控制這個符號的產生!
但是並不包含去顯示裡面的檔案!
要顯示裡面的檔案,就必須設定 OnTreeNodePopulate 的程式,用該程式來填入裡面的檔案(節點)。

 

所以這也是為何在產生根目錄的時候,
要先確定底下還有東西,才能把 PopulateOnDemand 設定為 True,
因為沒有設定好會有兩個後果:
1. 每個都設定為 True:即使父節點底下沒有任何子節點也會看到 [+] 的符號,
而且連最底層的節點也會有相同情形,必須一一點擊 [+] 符號才會消失。

2. 每個都設定為 False:永遠看不到父節點底下是否有子節點!

 

好啦...以上都了解之後,
接下來要說的是:當使用者點擊 [+] 之後,是如何產生子節點的?
這部分是藉由 TreeNodeEventArgs 產生的參數 e 傳入 OnTreeNodePopulate
 的副程式中:

1 void PopulateNode(Object sender, TreeNodeEventArgs e)
2     {
3         e.Node.ChildNodes.Clear();
4         PopulateProducts(e.Node);        
5     }

 

 

01 void PopulateProducts(TreeNode node)
02     {
03         DataSet ResultSet = RunQuery("SELECT * FROM nodeDB");
04         //
05         if (ResultSet.Tables.Count > 0)
06         {
07             foreach (DataRow row in ResultSet.Tables[0].Rows)
08             {
09                 // 建立新節點  
10                 //節點參數:(文字, 值, 節點圖片URL, 目標連結URL, 連結開啟方式)                
11                 TreeNode NewNode = new TreeNode(row["sevName"].ToString(), row["sevValue"].ToString(), "", row["serLink"].ToString(), "_self");
12                 //
13                 // 動態設定 PopulateOnDemand 值,
14                 // 在此節點底下仍有子節點時,才會產生展開符號                
15                 if (chkChildNodes(NewNode))
16                 {
17                     NewNode.PopulateOnDemand = true;
18                     //
19                     if (doChildNodeExpand(NewNode, queryString.Length)) //根據網址參數決定要展開的層數
20                     {
21                         NewNode.Expand(); //節點的位置在網址參數的路徑上
22                     }

23                     else
24                     {
25                         NewNode.Collapse();
26                     }

27                 }

28                 else
29                 {
30                     NewNode.PopulateOnDemand = false;
31                 }

32                 //
33                 NewNode.SelectAction = TreeNodeSelectAction.Expand;  // 設定點選節點時展開
34                 node.ChildNodes.Add(NewNode);    
35             }

36         }

37     }

分成兩段,
第一段是先清除所有節點,
第二段跟之前很類似,
首先要去資料庫找到其他節點填入,(3~11行)
之後根據該節點底下是否有其他節點決定 PopulateOnDemand 的值,(15, 17行)
至於要根據網址參數展開節點的副程式 doChildNodeExpand ,在這邊就先保留啦,
畢竟那是小弟的 CASE,對其他人來說也許不是很適用,
不過上面說的要是都有通...那樹狀圖也差不多會個七八成了,
我的方法比較粗淺而且大多都是由 MSDN 改寫的,
有問題歡迎指教。