Menu 是另一个支持层次化数据的富控件。它可以绑定到数据源(声明性的)或编程使用 MenuItem 对象来填充。
MenuItem 类不像 TreeNode 类那样丰富,它不支持复选框,也不能通过编程设置它们的 折叠/展开 状态。不过,它们也有很多相似的属性,包括那些用于设置图片、确定条目是否可选以及指定目标链接的属性。
MenuItem 的属性:
Text | 菜单中显示的文字 |
TooTip | 鼠标停留菜单项时的提示文字 |
Value | 保存不显示的额外数据(比如某些程序需要用到的 ID) |
NavigateUrl | 如果设置了值,单击节点会前进至此 Url。否则,需要响应 Menu.MenuItemClick事件确定要执行的活动 |
Target | 它设置了链接的目标窗口或框架。Menu 自身也暴露了 Target 属性设置所有的 MenuItem 实例的默认目标 |
Selectable | 如果为 false,菜单项不可选。通常只在菜单项有一些可选的子菜单项时,才设为 false |
ImageUrl | 菜单项旁边的图片 |
PopOutImageUrl | 菜单项包含子项时现在在菜单项旁的图片,默认是一个小的实心箭头 |
SeparatorImageUrl | 菜单项下面显示的图片,用于分隔菜单项 |
和遍历 TreeView 结构的方式相同,Menu 控件仅做很小的改动,几乎就可以重用先前 TreeView 的代码:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DataSet ds = GetProductsAndCategories();
foreach (DataRow row in ds.Tables["Categories"].Rows)
{
MenuItem itemCategory = new MenuItem(
row["CategoryName"].ToString(),
row["CategoryID"].ToString());
Menu1.Items.Add(itemCategory);
DataRow[] childRows = row.GetChildRows(ds.Relations["CatProds"]);
foreach (DataRow childRow in childRows)
{
MenuItem itemProduct = new MenuItem(
childRow["ProductName"].ToString(),
childRow["ProductID"].ToString());
itemCategory.ChildItems.Add(itemProduct);
}
}
}
}
protected void Menu1_MenuItemClick(object sender, MenuEventArgs e)
{
if (Menu1.SelectedItem.Depth == 0)
{
lblInfo.Text = "You selected Category ID: ";
}
else if (Menu1.SelectedItem.Depth == 1)
{
lblInfo.Text = "You selected Product ID: ";
}
lblInfo.Text += Menu1.SelectedItem.Value;
}
虽然 Menu 和 TreeView 的呈现方式非常不同,但它们暴露了非常相似的编程模型。它们还有相似的基于样式的格式化模型。
不过,它们还是有一些显著的差异:
- Menu 每次显示一个子菜单;TreeView 可以一次展开任意多个节点。
- Menu 在页面里显示第一层的链接;TreeView 显示页面上内联的所有项。
- Menu 不支持按需填充及客户端回调;TreeView 支持。
- Menu 支持模板;TreeView 不支持。
- Menu 支持水平和垂直布局;TreeView 只支持垂直布局。
Menu 样式
Menu 控件提供了数量惊人的样式。和 TreeView 一样,Menu 从 Style 基类派生了自定义类(实际上,它派生了 MenuStyle类 和 MenuItemStyle类)。
这些样式增加了间距属性 ItemSpacing、HorizontalPadding、VerticalPadding;但不能通过样式设置菜单项的图片,因为它没有 ImageUrl 属性。
Menu 在很大程度上和 TreeView 相似,它支持为位于不同层级的菜单定义不同的菜单样式。不过,一个主要的区别是 Menu 控件鼓励你区分静态项(菜单刚创建时就显示在页面上的第一层条目)和动态项(鼠标移动到菜单某个区域时被添加的弹出的菜单项)。
对于大多网站,这两个元素具有明显的区别。为了支持这些,Menu 定义了两组平行样式,如下:
StaticMenuStyle | DynamicMenuStyle | 设置总体“盒子”的外观,所有的菜单项出现在这里 |
StaticMenuItemStyle | DynamicMenuItemStyle | 设置单个菜单项的外观 |
StaticSelectedStyle | DynamicSelectedStyle | 设置选择项的外观,选择项指的是前一个被单击的项(且触发上一次回发的项) |
StaticHoverStyle | DynamicHoverStyle | 设置鼠标停留时项的外观 |
还可以设置层级特定的样式,这样每层的菜单和子菜单都不一样。可通过 3 个集合来设置:LevelMenuItemStyles、LevelSubMenuStyles、LevelSelectedStyles。这些集合分别作用于普通的菜单项,包含其他菜单项的菜单项以及被选择的菜单项。
可能你会觉得不必做那么多工作区分动态样式和静态样式。但考虑到 Menu 控件另一个非同寻常的功能,这一模型就很有意义:它允许设置静态层次的数目。在默认情况下,只有一个静态层,其他所有的项只有把鼠标停留在相应的父菜单上时才会弹出。不过,如果设置了 Menu.StaticDisplayLevels 属性,就可改变这一切。例如,如果设为 2 ,前两层菜单将使用静态样式呈现在页面上,还可用 StaticSubMenuIdent 属性控制每层的缩进。
在调整特定呈现方面,Menu 控件会暴露更多顶层属性。例如,可以设置菜单消失前的延时(DisappearAfter)、展开图标和分隔符的默认图片和滚动行为等。具体可以参考 Visual Studio 帮助文档获取属性的完整列表。
模板
通过 StaticMenuItemTemplate 和 DynamicMenuItemTemplate 属性,Menu 控件也能够支持模板。这些模板能让你完全控制每个菜单项要呈现的 HTML。
有趣的是,无论是以声明的方式还是编程的方式填充 Menu 类,都能够使用模板。从模板的角度来看,你总是绑定到 MenuItem 对象。也就是说,模板总是必须从 MenuItem.Text 属性抓取菜单项的值,如下所示:
<asp:Menu ID="Menu1" runat="server" >
<StaticItemTemplate>
<%# Eval("Text") %>
</StaticItemTemplate>
</asp:Menu>
你想使用 Menu 模板功能的另一个原因可能是显示来自数据对象的多个信息。例如,你可能希望同时在带单项上显示 SiteMapNode 的标题和描述。遗憾的是,这不可能。问题在于 Menu 直接绑定到 MenuItem 对象。MenuItem 确实暴露了 DataItem 属性,但当它被添加到菜单时,DataItem 不再引用当初用来填充它的 SiteMapNode。
如果确实非常希望这样显示,可以在类里写一个基于 URL 查找 SiteMapNode 的自定义方法。这本来不是必要的工作,不过它确实能够使菜单项模板获得描述信息:
private string matchingDescription;
protected string GetDescriptionFromTitle(string title)
{
SiteMapNode node = SiteMap.RootNode;
SearchNodes(node, title);
return matchingDescription;
}
private void SearchNodes(SiteMapNode node, string title)
{
if (node.Title == title)
{
matchingDescription = node.Description;
return;
}
else
{
foreach (SiteMapNode child in node.ChildNodes)
{
SearchNodes(child, title);
}
}
}
<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1" StaticDisplayLevels="2">
<StaticItemTemplate>
<%# Eval("Text") %><br />
<small>
<%# GetDescriptionFromTitle(((MenuItem)Container.DataItem).Text)%>
</small>
</StaticItemTemplate>
</asp:Menu>
你还可以为 Menu 控件声明一个数据绑定,它指定绑定对象中用于 MenuItem 文本的那个属性。不过,它只接受一个字段。尽管如此,它还是可以很方便的把标题显示为文本而把描述作为提示文本:
<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1" StaticDisplayLevels="2">
<DataBindings>
<asp:MenuItemBinding DataMember="SiteMapNode" TextField="Title" ToolTipField="Description" />
</DataBindings>
</asp:Menu>
注解:
在 ASP.NET 4 里,Menu 控件不再使用 HTML 表格来呈现自己,而是把自己呈现为一组无序的条目(使用 <ul> 和 <li> 元素),并通过样式规则来创建正确的格式。
Menu 控件在页面的顶部以样式块的形式呈现其所有样式,而不与呈现的 HTML 内联。但是,可以把 Menu.IncludeStyleBlock 属性设为 fasle 以告知 Menu 不要呈现其样式,这样能让你完全控制 Menu 样式,甚至可以采用外部样式表的样式。
(如果你需要一个起点,可以先把该属性设为 true 运行页面,复制呈现的 HTML 样式代码,根据需要做调整)