网站导航(TreeView 控件)
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 个值之一。每组都包含折叠、展开和没有子节点时要使用的图片。