TreeView 是讓人印象最深刻的導航控件之一,不僅因為它允許呈現富樹視圖,還因為它支持按需填入樹的部分(不需要刷新整個頁面)。但最重要的是,它支持很多樣式來改變它的外觀。
通過幾個基本的屬性,可以把 TreeView 從一個幫助主題索引變成一個文件或文件夾目錄列表。實際上,TreeView 根本不必呈現為一棵樹,通過一點點樣式設置,它可以呈現非縮進層次的數據,比如應用程序目錄表。
可以使用 TreeView 顯示綁定的 XML 數據,顯示站點地圖數據,這是它綁定層次化數據源的能力。但還可以綁定一個普通的數據源來填充 TreeView(只會得到第一層節點),你還可以自己創造節點,通過編程或在 .aspx 頁面聲明。
<asp:TreeView ID="TreeView1" runat="server">
<Nodes>
<asp:TreeNode Text="Products">
<asp:TreeNode Text="Hardware"></asp:TreeNode>
</asp:TreeNode>
<asp:TreeNode Text="Services"></asp:TreeNode>
</Nodes>
</asp:TreeView>
TreeView 第一次顯示時所有的節點都會出現。設置 TreeView.ExpandDepth 屬性來控制這一行為。如果 ExpandDepth = 2,則只有0,1,2 這三層被顯示。
MaxDataBindDepth 屬性可以控制 TreeView 總共包含多少層(展開的或折疊的),MaxDataBindDepth 默認 -1,你可以查看整棵樹。如果是 2,你只能看到起始節點下的 2 層。
通過編程設置 TreeNode.Expanded 屬性來打開或折疊節點。
TreeNode
TreeNode 對象表示樹的每一個節點。TreeNode 提供了導航屬性,如 ChildNodes 和 Parent,除了這些基本屬性外,還提供了下表的所有實用屬性:
Text | 節點顯示的文字 |
ToolTip | 鼠標停留節點文本上顯示提示文字 |
Value | 保存關於節點的不顯示的額外數據(比如單擊事件用於識別節點或查找更多信息的唯一 ID) |
NavigateUrl | 如果設置了值,單擊后會前進至此 URL。 否則,需要響應 TreeView.SelectedNodeChanged 事件以便確定要執行的活動。 |
Target | 如果設置了 NavigateUrl 屬性,它會設置鏈接的目標窗口或框架。如果沒有設置 Target,新頁面在當前窗口打開。TreeView 控件本身也暴露了 Target 屬性用來設置所有節點的默認目標。 |
ImageUrl | 顯示在節點旁邊的圖片 |
ImageToolTip | 該圖片的提示信息 |
TreeNode 有一個不尋常的細節是,它有兩個模式:
- 選擇模式:單擊節點會回發頁面並引發 TreeView.SelectedNodeChanged 事件。(這是所有節點的默認模式)
- 導航模式:單擊后導航到新頁面,不會觸發上述事件。只要 NavigateUrl 屬性非空,TreeNode 就會處於導航模式。
注:
綁定到站點地圖的 TreeNode 處於導航模式,因為每個站點地圖節點提供一個 URL 信息。
下面這個示例用數據庫查詢結果填充 TreeView,利用 TreeView 能夠顯示層次化數據的能力創建一個主從表。ASP.NET 沒有提供任何查詢數據庫並按層次化顯示結果的數據源控件,所有不能進行數據綁定,必須通過編程來查詢表手工創建 TreeNode 結構:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DataSet ds = GetProductsAndCategories();
foreach (DataRow row in ds.Tables["Categories"].Rows)
{
TreeNode nodeCategory = new TreeNode(
row["CategoryName"].ToString(),
row["CategoryID"].ToString());
TreeView1.Nodes.Add(nodeCategory);
DataRow[] childRows = row.GetChildRows(ds.Relations["CatProds"]);
foreach (DataRow childRow in childRows)
{
TreeNode nodeProduct = new TreeNode(
childRow["ProductName"].ToString(),
childRow["ProductID"].ToString());
nodeCategory.ChildNodes.Add(nodeProduct);
}
nodeCategory.Collapse();
}
}
}
private DataSet GetProductsAndCategories()
{
string conStr = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
SqlConnection conn = new SqlConnection(conStr);
string sqlCat = "SELECT CategoryID, CategoryName FROM Categories";
string sqlProd = "SELECT ProductID, ProductName, CategoryID FROM Products";
SqlDataAdapter sda = new SqlDataAdapter(sqlCat, conn);
DataSet ds = new DataSet();
try
{
conn.Open();
sda.Fill(ds, "Categories");
sda.SelectCommand.CommandText = sqlProd;
sda.Fill(ds, "Products");
}
finally
{
conn.Close();
}
DataRelation relation = new DataRelation("CatProds",
ds.Tables["Categories"].Columns["CategoryID"],
ds.Tables["Products"].Columns["CategoryID"]);
ds.Relations.Add(relation);
return ds;
}
protected void TreeView1_SelectedNodeChanged(object sender, EventArgs e)
{
if (TreeView1.SelectedNode == null)
return;
if (TreeView1.SelectedNode.Depth == 0)
{
lblInfo.Text = "You selected Category ID: ";
}
else if (TreeView1.SelectedNode.Depth == 1)
{
lblInfo.Text = "You selected Product ID: ";
}
lblInfo.Text += TreeView1.SelectedNode.Value;
}
按需填充節點
你可能不希望一次填充大量的數據至所有的節點,這會大大增加處理第一次請求的時間,還會顯著增大頁面和試圖狀態的大小。
TreeView 有一個按需填充的功能,可以在節點打開時填充樹的分支。更妙的是,隨時可以填充樹的選定部分。要使用按需填充,需要把最后時刻想要填入的內容的 TreeNode 的 PopulateOnDemand 屬性設為 true。用戶展開這個分支時,TreeView 會引發 TreeNodePopulate 事件,在該事件里可以加入下一層節點。
TreeView 支持兩種按需填入節點的技術(客戶端回調 或 頁面回發):
- 當 TreeView.PopulateNodesFromClient 屬性為 true 的時候(默認),TreeView 執行一個客戶端的回調從你的事件獲得它需要的節點,而並不需要回發整個頁面。
- 當 上述屬性為 false,或者為 true 但瀏覽器不支持客戶端回調,那么 TreeView 會觸發一次正常的回發以獲得相同的結果。唯一的區別是整個頁面的刷新產生了一個略微不平滑的界面。
下面這個示例使用按需填充,先獲取類別節點,然后按需填充它們的子節點:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DataTable dtCategories = GetCategories();
foreach (DataRow row in dtCategories.Rows)
{
TreeNode nodeCategory = new TreeNode(
row["CategoryName"].ToString(),
row["CategoryID"].ToString());
nodeCategory.PopulateOnDemand = true;
nodeCategory.Collapse();
TreeView1.Nodes.Add(nodeCategory);
}
}
}
private DataTable GetCategories()
{
string conStr = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
SqlConnection conn = new SqlConnection(conStr);
string sqlCat = "SELECT CategoryID, CategoryName FROM Categories";
SqlDataAdapter sda = new SqlDataAdapter(sqlCat, conn);
DataSet ds = new DataSet();
try
{
conn.Open();
sda.Fill(ds, "Categories");
}
finally
{
conn.Close();
}
return ds.Tables["Categories"];
}
protected void TreeView1_SelectedNodeChanged(object sender, EventArgs e)
{
if (TreeView1.SelectedNode == null)
return;
if (TreeView1.SelectedNode.Depth == 0)
{
lblInfo.Text = "You selected Category ID: ";
}
else if (TreeView1.SelectedNode.Depth == 1)
{
lblInfo.Text = "You selected Product ID: ";
}
lblInfo.Text += TreeView1.SelectedNode.Value;
}
protected void TreeView1_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
// 如果有多個類型的節點需要填充,你可以檢查 Depth,本例不需要
Int32 categoryID = Int32.Parse(e.Node.Value);
DataTable dtProducts = GetProducts(categoryID);
foreach (DataRow row in dtProducts.Rows)
{
TreeNode nodeProduct = new TreeNode(
row["ProductName"].ToString(),
row["ProductID"].ToString());
e.Node.ChildNodes.Add(nodeProduct);
}
}
private DataTable GetProducts(int categoryID)
{
string conStr = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
SqlConnection conn = new SqlConnection(conStr);
string sqlProd = "SELECT ProductID, ProductName, CategoryID FROM Products";
SqlDataAdapter sda = new SqlDataAdapter(sqlProd, conn);
DataSet ds = new DataSet();
try
{
conn.Open();
sda.Fill(ds, "Products");
}
finally
{
conn.Close();
}
return ds.Tables[0];
}
一個給定的節點只會按需填充一次,此后,值保存在客戶端,同一節點再次折疊或展開時不會再次執行回調。
TreeView 樣式
TreeView 有一個細化的樣式模型,它允許你完全控制 TreeView 的外觀。每個樣式作用於一種節點,樣式由 TreeNodeStyle 類表示,它繼承自更常規的 Style 類。
和其他富控件一樣,通過樣式可以設置前景色、背景色、字體、邊框。
此外,TreeNodeStyle 還引入下表所列的特定節點的樣式屬性:
ImageUrl | 節點旁邊顯示的圖片 |
NodeSpacing | 當前節點與相鄰節點的垂直距離 |
VerticalPadding | 節點文字與節點邊界內部的垂直距離 |
HorizontalPadding | 節點文字與節點邊界內部的水平距離 |
ChildNodesPadding | 展開的父節點的最后一個子節點和其下一個兄弟節點的間距 |
TreeView 用 HTML 表哥呈現,因此你可以設置各個元素的間距來控制文字周圍的邊距和節點間的間距。另一個重要的屬性是 TreeView.NodeIndent,它設置樹結構里各個子層級間縮進的像素數。
TreeView 還允許通過高級屬性配置它的某些內部呈現。
- 用 TreeView.ShowExpandCollapse 屬性關閉樹中的節點列。
- 用 CollapseImageUrl 和 ExpandImageUrl 設置 TreeView 折疊和展開的指示器(通常由加號和減號圖標表示)。
- 用 NoExpandImageUrl 設置沒有子節點的節點旁顯示的圖片。
- 設置 TreeView.ShowCheckBoxes 為 true,所有節點邊出現復選框。
- 設置 TreeNode.ShowCheckBox 為 true,單個節點邊出現復選框。
1. 把樣式應用到節點類型
要對樹的所有節點應用樣式,可以使用 TreeView.NodeStyle 屬性。
要以更特定的樣式獨立設置 TreeView 的區域,見下表:
NodeStyle | 應用到所有節點 |
RootNodeStyle | 僅應用到第一層 ( 根 ) 節點 |
ParentNodeStyle | 應用到所有包含其他節點的節點,但不包括根節點 |
LeafNodeStyle | 應用到所有不包含子節點而且不是根據點的節點 |
SelectedNodeStyle | 應用到當前選中的節點 |
HoverNodeStyle | 應用到鼠標停留的節點. |
樣式在表中的順序從最通用到最特定。SelectedNodeStyle 會覆蓋 RootNodeStyle 里任何有沖突的設置。不過 RootNodeStyle 和 ParentNodeStyle 和 LeafNodeStyle 從來都不會產生沖突,因為根節點、父節點和子節點的定義是互斥的。例如,一個節點不能既是父節點又是根節點(TreeView 只把它看作根節點)。
2. 把樣式運用到節點層級
能夠對不同類型的節點應用樣式很有趣,不過一個更有用的功能是可基於節點的不同層級應用樣式。畢竟大多的樹都使用了嚴格的層級(例如,第一層代表類別,第二層代表產品等)。此時,確定某個節點是否有子節點並不重要,相反,確定節點的深度很重要。
唯一的問題是,TreeView 理論上具有不受限制的節點層次。所以,暴露 FirstLevelStyle、SecondLevelStyle 之類的屬性沒有意義。相反,TreeView 有一個 LevelStyles 集合,它可包含你期望的項數量(第一個條目代表根層,第二個條目代表節點的第二層等)。
例如,下面這個 TreeView 沒有使用任何縮進,而是通過應用不同的間距和字體使各層都各不相同:
<asp:TreeView ID="TreeView1" runat="server" HoverNodeStyle-Font-Underline="true"
ShowExpandCollapse="false" NodeIndent="0" OnSelectedNodeChanged="TreeView1_SelectedNodeChanged">
<LevelStyles>
<asp:TreeNodeStyle ChildNodesPadding="10" Font-Bold="true" Font-Size="12pt" ForeColor="DarkGreen" />
<asp:TreeNodeStyle ChildNodesPadding="5" Font-Bold="true" Font-Size="10pt" />
<asp:TreeNodeStyle ChildNodesPadding="5" Font-Underline="true" Font-Size="10pt" />
</LevelStyles>
</asp:TreeView>
3. TreeView 圖片
可以通過 TreeViewNode.ImageUrl 為單個節點設置圖片。但要給整個樹設置一組一致的圖片,就不需要使用這種細化的方法。
可以通過 3 個 TreeView 屬性為整個樹設置圖片:
- CollapseImageUrl :所有折疊節點的圖片
- ExpandImageUrl :所有展開節點的圖片
- NoExpandImageUrl :沒有子節點因此不能展開的節點的圖片
如果設置了上述屬性,並通過 TreeViewNode.ImageUrl 屬性為特定節點指定了圖片,節點的特定圖片將優先使用。
如果不想創建自定義的節點圖片,TreeView 還自帶了圖片。訪問這些圖片需要使用 TreeView.ImageSet 屬性,它接收來自 TreeViewImageSet 枚舉的 16 個值之一。每組都包含折疊、展開和沒有子節點時要使用的圖片。