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 樣式代碼,根據需要做調整)

