ASP.NET 横向菜单控件






4.72/5 (39投票s)
一篇关于如何使用 ASP.NET 构建带访问键和目标窗口支持的横向菜单控件的文章。
引言
几周前,我正在开发一个 ASP.NET Web 应用程序,需要一个带有子菜单的简单横向菜单。我决定使用 ASP.NET 的 Menu
控件,然后将其拖放到页面上。这很简单,但该控件不为菜单项提供访问键和目标窗口支持。我编写了一个教程,介绍如何
- 包含
accesskey
属性 - 包含
target
属性 - 包含站点地图路径
入门
这是我的项目结构。欢迎下载演示。
整合所有内容
首先,为网站项目添加一个站点地图。打开 web.sitemap 文件并用您的导航数据和结构填充它。要加下划线菜单标题中的某个字符,我们可以使用 HTML 下划线标签(<u></u>
)。为了完美地解析 XML,我们必须将小于号 (<) 替换为 & lt;(无空格)。然后,在每个 siteMapNode
中包含 accesskey
和 target
属性及其值。请参阅下面的示例。
站点地图
<siteMapNode>
<siteMapNode url="Default.aspx"
title="& lt;u>H& lt;/u>ome"
description="Home"
accesskey="H" />
<siteMapNode url="~/Views/Menu1.aspx"
title="& lt;u>M& lt;/u>enu1"
description="Menu1" accesskey="M" />
<siteMapNode url="~/Views/Menu2.aspx"
title="M<u>e</u>nu2"
description="Menu2" accesskey="E" />
<siteMapNode url="~/Views/Menu3.aspx"
title="Me<u>n</u>u3"
description="Menu3"
accesskey="N" target="_blank" />
<siteMapNode url="~/Views/Menu4.aspx"
title="Men<u>u</u>4"
description="Menu4" accesskey="U">
<siteMapNode url="~/Views/Menu4Sub1.aspx"
title="Menu4<u>S</u>ub1"
description="Menu4Sub1"
accesskey="S" />
<siteMapNode url="~/Views/Menu4Sub2.aspx"
title="Menu4Su<u>b</u>2"
description="Menu4Sub2"
target="_blank" accesskey="B" />
</siteMapNode>
……
….
</siteMapNode>
</siteMap>
主页模板
为网站项目添加一个母版页。将 SiteMapDataSource
控件拖放到页面上,然后将 Menu
控件拖放到页面上,并将 Menu
控件包装在 div
标签中。您可以在 此处找到每个菜单属性的详细说明。设置 staticdisplaylevels ="2"
和 orientation="Horizontal"
以水平模式显示菜单控件。我们可以使用内联样式表,或者将 CSS 样式放在外部文件中。在本教程中,CSS 样式位于 style.css 文件中。请参阅列表 2。
<asp:SiteMapDataSource id="MenuSource" runat="server" />
<div class="background">
<asp:menu id="NavigationMenu" CssClass="NavigationMenu"
staticdisplaylevels="2" DynamicHorizontalOffset="1"
staticsubmenuindent="1px" MaximumDynamicDisplayLevels="4"
orientation="Horizontal"
DynamicPopOutImageUrl="~/Images/right-arrow.gif"
StaticPopOutImageUrl="~/Images/drop-arrow.gif"
datasourceid="MenuSource"
runat="server" Height="30px">
<staticmenuitemstyle ItemSpacing="10"
CssClass="staticMenuItemStyle"/>
<statichoverstyle CssClass="staticHoverStyle" />
<StaticSelectedStyle CssClass="staticMenuItemSelectedStyle"/>
<DynamicMenuItemStyle CssClass="dynamicMenuItemStyle" />
<dynamichoverstyle CssClass="menuItemMouseOver" />
<DynamicMenuStyle CssClass="menuItem" />
<DynamicSelectedStyle CssClass="menuItemSelected" />
<DataBindings>
<asp:MenuItemBinding DataMember="siteMapNode"
NavigateUrlField="url" TextField="title"
ToolTipField="description" />
</DataBindings>
</asp:menu>
</div>
将 SiteMapPath
控件拖放到页面上。此控件的目的是显示一个导航路径,向用户显示当前页面的位置。请参阅列表 3。
<div id="e">
<asp:SiteMapPath ID="SiteMapPath1" runat="server"
RenderCurrentNodeAsLink="true"
CssClass="currentNodeStyle"
PathSeparator=" >> ">
<PathSeparatorStyle ForeColor="#5D7B9D" CssClass="currentNodeStyle" />
<CurrentNodeStyle ForeColor="#333333" CssClass="currentNodeStyle" />
<NodeStyle ForeColor="#7C6F57" CssClass="currentNodeStyle" />
<RootNodeStyle ForeColor="#5D7B9D" CssClass="currentNodeStyle" />
</asp:SiteMapPath>
</div>
母版页代码隐藏
在 Page_Load
事件中包含 MenuItemDataBound
和 SiteMapResolve
事件处理程序。前一个事件的目的是在菜单项被渲染或显示在 Menu
控件中之前插入 target
属性值并创建菜单项的访问键。后一个事件用于修改 SiteMapPath
控件显示的文本。
NavigationMenu.MenuItemDataBound +=
new MenuEventHandler(NavigationMenu_MenuItemDataBound);
SiteMap.SiteMapResolve +=
new SiteMapResolveEventHandler(SiteMap_SiteMapResolve);
下面是 NavigationMenu_MenuItemDataBound
方法的实现。当 Menu
控件中的菜单项绑定到数据时,会发生 MenuItemDataBound
事件。也就是说,它将遍历每个 siteMapNode
并查找 accesskey
和 target
属性。菜单项有一个关联的 target
属性,我们可以使用 target
属性值来设置其目标窗口。请参阅列表 5。
void NavigationMenu_MenuItemDataBound(object sender, MenuEventArgs e)
{
SiteMapNode node = (SiteMapNode)e.Item.DataItem;
//set the target of the navigation menu item (blank, self, etc...)
if (node["target"] != null)
{
e.Item.Target = node["target"];
}
//create access key button
if (node["accesskey"] != null)
{
CreateAccessKeyButton(node["accesskey"] as string, node.Url);
}
}
为了使访问键生效,请在母版页中添加一个 Panel
控件,并添加一个 JavaScript 函数以将网页重定向到指定的页面。请参阅下文。
<asp:Panel ID="AccessKeyPanel" runat="server" />
<script type="text/javascript">
function navigateTo(url) {
window.location = url;
}
</script>
下面是 CreateAccessKeyButton
方法的实现。动态创建一个 HtmlButton
控件并为其附加一个 onclick
事件。将 style.left
属性设置为 -2555px 以隐藏该控件。可在 此处找到不同浏览器中访问键的完整列表。
//create access key button
void CreateAccessKeyButton(string ak, string url)
{
HtmlButton inputBtn = new HtmlButton();
inputBtn.Style.Add("width", "1px");
inputBtn.Style.Add("height", "1px");
inputBtn.Style.Add("position", "absolute");
inputBtn.Style.Add("left", "-2555px");
inputBtn.Style.Add("z-index", "-1");
inputBtn.Attributes.Add("type", "button");
inputBtn.Attributes.Add("value", "");
inputBtn.Attributes.Add("accesskey", ak);
inputBtn.Attributes.Add("onclick", "navigateTo('" + url + "');");
AccessKeyPanel.Controls.Add(inputBtn);
}
当访问 CurrentNode
属性时,会触发 SiteMap.SiteMapResolve
事件。它将递归调用 ReplaceNodeText
方法并替换 HTML 下划线标签。请参阅列表 8。
SiteMapNode SiteMap_SiteMapResolve(object sender, SiteMapResolveEventArgs e)
{
if (SiteMap.CurrentNode != null)
{
SiteMapNode currentNode = SiteMap.CurrentNode.Clone(true);
SiteMapNode tempNode = currentNode;
tempNode = ReplaceNodeText(tempNode);
return currentNode;
}
return null;
}
//remove <u></u> tag recursively
internal SiteMapNode ReplaceNodeText(SiteMapNode smn)
{
//current node
if (smn != null && smn.Title.Contains("<u>"))
{
smn.Title = smn.Title.Replace("<u>",
"").Replace("</u>", "");
}
//parent node
if (smn.ParentNode != null)
{
if (smn.ParentNode.Title.Contains("<u>"))
{
SiteMapNode gpn = smn.ParentNode;
smn.ParentNode.Title = smn.ParentNode.Title.Replace(
"<u>", "").Replace("</u>", "");
smn = ReplaceNodeText(gpn);
}
}
return smn;
}
使用代码
由于菜单位于母版页中,右键单击网站项目,选择“添加新项”,选择“Web 窗体”,然后选中“选择母版页”复选框。
关注点
悬停菜单似乎在移动设备上不起作用。为了解决这个问题,我包含了一个 TreeView
控件并将其 Visible
属性设置为 false
。此控件默认展开其所有节点。这将解决上述问题。在代码隐藏中,如果请求的浏览器是移动设备,则隐藏 Menu
控件并显示 TreeView
控件。请参阅列表 9。
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Browser.IsMobileDevice)
{
NavigationMenu.Visible = false;
NavigationTreeView.Visible = true;
}
}
当我尝试在 IE 8 中测试菜单时,悬停菜单未正确渲染。为了解决这个问题,我将 DynamicMenuStyle
的 z-index 设置为 200,请参阅 style.css。子菜单在 Google Chrome 中不起作用。经过一番研究,我找到了解决方案。请参阅列表 10。
protected void Page_Load(object sender, EventArgs e)
{
if (Request.UserAgent.IndexOf("AppleWebKit") > 0)
{
Request.Browser.Adapters.Clear();
NavigationMenu.DynamicMenuStyle.Width = Unit.Pixel(120);
}
}
新更新
我收到读者关于菜单控件在 Safari 和 Google Chrome 浏览器中显示不正确的几条投诉。不知何故,菜单项堆叠在一起,子菜单的宽度出现了间隔。经过一些研究,我在 此处找到了答案,请参阅列表 11。要修复子菜单宽度,请从 CSS 文件中的 dynamicMenuItemStyle
中删除 display:block
。
protected override void AddedControl(Control control, int index)
{
if (Request.ServerVariables["http_user_agent"].IndexOf("Safari",
StringComparison.CurrentCultureIgnoreCase) != -1)
this.Page.ClientTarget = "uplevel";
base.AddedControl(control, index);
}
我还使用 Vincent Van Zyl 的代码重写了检测移动浏览器的逻辑。请参阅列表 12。
public static readonly string[] mobiles =
new[]
{
"midp", "j2me", "avant", "docomo",
"novarra", "palmos", "palmsource",
"240x320", "opwv", "chtml",
"pda", "windows ce", "mmp/",
"blackberry", "mib/", "symbian",
"wireless", "nokia", "hand", "mobi",
"phone", "cdm", "up.b", "audio",
"SIE-", "SEC-", "samsung", "HTC",
"mot-", "mitsu", "sagem", "sony"
, "alcatel", "lg", "eric", "vx",
"NEC", "philips", "mmm", "xx",
"panasonic", "sharp", "wap", "sch",
"rover", "pocket", "benq", "java",
"pt", "pg", "vox", "amoi",
"bird", "compal", "kg", "voda",
"sany", "kdd", "dbt", "sendo",
"sgh", "gradi", "jb", "dddi",
"moto", "iphone"
};
public static bool isMobileBrowser()
{
//GETS THE CURRENT USER CONTEXT
HttpContext context = HttpContext.Current;
//FIRST TRY BUILT IN ASP.NT CHECK
if (context.Request.Browser.IsMobileDevice)
{
return true;
}
//THEN TRY CHECKING FOR THE HTTP_X_WAP_PROFILE HEADER
if (context.Request.ServerVariables["HTTP_X_WAP_PROFILE"] != null)
{
return true;
}
//THEN TRY CHECKING THAT HTTP_ACCEPT EXISTS AND CONTAINS WAP
if (context.Request.ServerVariables["HTTP_ACCEPT"] != null &&
context.Request.ServerVariables["HTTP_ACCEPT"].ToLower().Contains("wap"))
{
return true;
}
//AND FINALLY CHECK THE HTTP_USER_AGENT
//HEADER VARIABLE FOR ANY ONE OF THE FOLLOWING
if (context.Request.ServerVariables["HTTP_USER_AGENT"] != null)
{
for (int i = 0; i < mobiles.Length; i++)
{
if (context.Request.ServerVariables["HTTP_USER_AGENT"].
ToLower().Contains(
mobiles[i].ToLower()))
{
return true;
}
}
}
return false;
}
结论
如果您发现任何错误或不同意内容,请给我留言,我将与您一起纠正。
已在 IE 6.0/7.0/8.0、Google Chrome、Safari 和 Firefox 上测试。
历史
- 2010/02/25 - 根据 The Code Project 会员 kentex2000 的建议,移除了根节点之前的
SiteMapPath
的PathSeparator
。在SiteMap_SiteMapResolve
方法中添加了SiteMap.CurrentNode != null
。 - 2010/02/03 - 修复了 Safari 和 Google Chrome 浏览器中的菜单显示问题,并添加了检测移动浏览器的新逻辑。