展示一些簡單的樹狀圖用法
這功能摸了很久,
主要是要針對上面交代下來的功能去做客製化,
弄到後面真的有一把鼻涕一把眼淚的感覺...
這邊只是做個紀錄,小弟功力火侯還不到家,
希望路過的各位高手多多指教。
在開始之前先說明一下客製化的需求:
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)
讓程式在頁面載入可以去抓這些資訊:
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)
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)
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
}
決定根目錄是否展開的副程式↓:
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 好了:
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 的副程式中:
void PopulateNode(Object sender, TreeNodeEventArgs e) 2
{ 3
e.Node.ChildNodes.Clear(); 4
PopulateProducts(e.Node); 5
}
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 改寫的,
有問題歡迎指教。
