编程嵌套列表
Programmatic nested list
我正在尝试使用 ListView 控件在 Asp.Net/C# 中创建嵌套列表。我看了很多例子,但我似乎无法理解它们。
模式如下:
<ul>
<li>Item 1 - level 1</li>
<li>Item 2 - level 1</li>
<li>Item 3 - level 1
<ul>
<li>Item 1 - level 2</li>
<li>Item 2 - level 2</li>
<li>Item 3 - level 2</li>
</ul>
</li>
</ul>
我创建了一个列表,它使用一个级别,因此它没有正确嵌套 html - 只是 <li>
个标签的平面列表。
但是,我想进行更改。
只有一个级别会有子菜单 - 某些级别 1 的项目可能没有任何子项目 - 所以我正在寻找可以呈现 0 或 1 个子菜单的模式。
有人知道我需要用下面的代码做什么来实现我想要的吗?
<asp:ListView ID="lv" runat="server"
OnItemDataBound="LV_ItemDataBound">
<LayoutTemplate>
<nav>
<ul class="content-nav">
<asp:PlaceHolder ID="itemPlaceholder" runat="server" />
</ul>
</nav>
</LayoutTemplate>
<ItemTemplate>
<li id="liMenuItem" runat="server">
<asp:HyperLink ID="lnkMenuItem" runat="server"
CssClass="content-nav_link"></asp:HyperLink>
</li>
</ItemTemplate>
</asp:ListView>
protected void LV_ItemDataBound(object source, ListViewItemEventArgs e)
{
var item = e.Item;
if (item.ItemType == ListViewItemType.DataItem)
{
var data = (ContentNavItem)item.DataItem;
var liMenuItem = item.GetControl<HtmlGenericControl>("liMenuItem");
// Do something with the item here
}
}
void Build()
{
var currentId = MenuItems.First(x => x.Route == CurrentUrl).Id;
var currentItems = MenuItems
.Where(x => x.IsTopLevel || x.ParentId == currentId)
.OrderBy(x => x.GroupId).ThenBy(x => x.Anchor);
lv.DataSource = currentItems;
lv.DataBind();
}
将菜单创建为带有 StringBuilder
的字符串会容易得多。但是如果你真的想要递归控件,你可以克隆控件并 re-use 它用于每个菜单级别。我在此演示中使用 Repeater,因为它更适合重复项目。所以首先我们创建一个 class 和一些演示数据
public class MenuItem
{
public string name { get; set; }
public List<MenuItem> menuitems { get; set; }
}
用虚拟数据填充它
protected void Page_Load(object sender, EventArgs e)
{
List<MenuItem> MenuItems = new List<MenuItem>();
for (int i = 0; i < 10; i++)
{
MenuItems.Add(new MenuItem() { name = "Name " + i });
if (i == 2 || i == 5 || i == 8)
{
MenuItems[i].menuitems = new List<MenuItem>();
MenuItems[i].menuitems.Add(new MenuItem() { name = "SubName 1" });
MenuItems[i].menuitems.Add(new MenuItem() { name = "SubName 2" });
MenuItems[i].menuitems.Add(new MenuItem() { name = "SubName 3" });
if (i == 8)
{
MenuItems[i].menuitems[0].menuitems = new List<MenuItem>();
MenuItems[i].menuitems[0].menuitems.Add(new MenuItem() { name = "SubName 1" });
MenuItems[i].menuitems[0].menuitems.Add(new MenuItem() { name = "SubName 2" });
MenuItems[i].menuitems[0].menuitems.Add(new MenuItem() { name = "SubName 3" });
}
}
}
Repeater1.DataSource = MenuItems;
Repeater1.DataBind();
}
将 Repeater 控件添加到页面。还有一个 PlaceHolder,这样它就可以保存自己的副本。
<nav>
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
<HeaderTemplate>
<ul class="content-nav">
</HeaderTemplate>
<ItemTemplate>
<li id="liMenuItem" runat="server">
<%# Eval("name") %>
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</nav>
现在在 ItemDataBound 事件中,我们克隆了 Repeater1
,用子菜单填充它,将它添加到 parent Repeater 的 PlaceHolder 并附加一个 ItemDataBound programaticallty,这样 Repeater 就可以生成它自己的 children.
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
MenuItem item = e.Item.DataItem as MenuItem;
//check if the item exists and if it has subitems
if (item != null && item.menuitems != null)
{
//determine type and get the properties
Type type = sender.GetType();
PropertyInfo[] properties = type.GetProperties();
Object obj = type.InvokeMember("", BindingFlags.CreateInstance, null, sender, null);
//copy the properties
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(obj, propertyInfo.GetValue(sender, null), null);
}
}
//cast the created object back to a repeater
Repeater nestedRepeater = obj as Repeater;
//fill the child repeater with the sub menu items
nestedRepeater.DataSource = item.menuitems;
//attach the itemdatabound event
nestedRepeater.ItemDataBound += Repeater1_ItemDataBound;
//bind the data
nestedRepeater.DataBind();
//find the placeholder and add the created Repeater
PlaceHolder ph = e.Item.FindControl("PlaceHolder1") as PlaceHolder;
ph.Controls.Add(nestedRepeater);
}
}
我正在尝试使用 ListView 控件在 Asp.Net/C# 中创建嵌套列表。我看了很多例子,但我似乎无法理解它们。
模式如下:
<ul>
<li>Item 1 - level 1</li>
<li>Item 2 - level 1</li>
<li>Item 3 - level 1
<ul>
<li>Item 1 - level 2</li>
<li>Item 2 - level 2</li>
<li>Item 3 - level 2</li>
</ul>
</li>
</ul>
我创建了一个列表,它使用一个级别,因此它没有正确嵌套 html - 只是 <li>
个标签的平面列表。
但是,我想进行更改。
只有一个级别会有子菜单 - 某些级别 1 的项目可能没有任何子项目 - 所以我正在寻找可以呈现 0 或 1 个子菜单的模式。
有人知道我需要用下面的代码做什么来实现我想要的吗?
<asp:ListView ID="lv" runat="server"
OnItemDataBound="LV_ItemDataBound">
<LayoutTemplate>
<nav>
<ul class="content-nav">
<asp:PlaceHolder ID="itemPlaceholder" runat="server" />
</ul>
</nav>
</LayoutTemplate>
<ItemTemplate>
<li id="liMenuItem" runat="server">
<asp:HyperLink ID="lnkMenuItem" runat="server"
CssClass="content-nav_link"></asp:HyperLink>
</li>
</ItemTemplate>
</asp:ListView>
protected void LV_ItemDataBound(object source, ListViewItemEventArgs e)
{
var item = e.Item;
if (item.ItemType == ListViewItemType.DataItem)
{
var data = (ContentNavItem)item.DataItem;
var liMenuItem = item.GetControl<HtmlGenericControl>("liMenuItem");
// Do something with the item here
}
}
void Build()
{
var currentId = MenuItems.First(x => x.Route == CurrentUrl).Id;
var currentItems = MenuItems
.Where(x => x.IsTopLevel || x.ParentId == currentId)
.OrderBy(x => x.GroupId).ThenBy(x => x.Anchor);
lv.DataSource = currentItems;
lv.DataBind();
}
将菜单创建为带有 StringBuilder
的字符串会容易得多。但是如果你真的想要递归控件,你可以克隆控件并 re-use 它用于每个菜单级别。我在此演示中使用 Repeater,因为它更适合重复项目。所以首先我们创建一个 class 和一些演示数据
public class MenuItem
{
public string name { get; set; }
public List<MenuItem> menuitems { get; set; }
}
用虚拟数据填充它
protected void Page_Load(object sender, EventArgs e)
{
List<MenuItem> MenuItems = new List<MenuItem>();
for (int i = 0; i < 10; i++)
{
MenuItems.Add(new MenuItem() { name = "Name " + i });
if (i == 2 || i == 5 || i == 8)
{
MenuItems[i].menuitems = new List<MenuItem>();
MenuItems[i].menuitems.Add(new MenuItem() { name = "SubName 1" });
MenuItems[i].menuitems.Add(new MenuItem() { name = "SubName 2" });
MenuItems[i].menuitems.Add(new MenuItem() { name = "SubName 3" });
if (i == 8)
{
MenuItems[i].menuitems[0].menuitems = new List<MenuItem>();
MenuItems[i].menuitems[0].menuitems.Add(new MenuItem() { name = "SubName 1" });
MenuItems[i].menuitems[0].menuitems.Add(new MenuItem() { name = "SubName 2" });
MenuItems[i].menuitems[0].menuitems.Add(new MenuItem() { name = "SubName 3" });
}
}
}
Repeater1.DataSource = MenuItems;
Repeater1.DataBind();
}
将 Repeater 控件添加到页面。还有一个 PlaceHolder,这样它就可以保存自己的副本。
<nav>
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
<HeaderTemplate>
<ul class="content-nav">
</HeaderTemplate>
<ItemTemplate>
<li id="liMenuItem" runat="server">
<%# Eval("name") %>
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</nav>
现在在 ItemDataBound 事件中,我们克隆了 Repeater1
,用子菜单填充它,将它添加到 parent Repeater 的 PlaceHolder 并附加一个 ItemDataBound programaticallty,这样 Repeater 就可以生成它自己的 children.
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
MenuItem item = e.Item.DataItem as MenuItem;
//check if the item exists and if it has subitems
if (item != null && item.menuitems != null)
{
//determine type and get the properties
Type type = sender.GetType();
PropertyInfo[] properties = type.GetProperties();
Object obj = type.InvokeMember("", BindingFlags.CreateInstance, null, sender, null);
//copy the properties
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(obj, propertyInfo.GetValue(sender, null), null);
}
}
//cast the created object back to a repeater
Repeater nestedRepeater = obj as Repeater;
//fill the child repeater with the sub menu items
nestedRepeater.DataSource = item.menuitems;
//attach the itemdatabound event
nestedRepeater.ItemDataBound += Repeater1_ItemDataBound;
//bind the data
nestedRepeater.DataBind();
//find the placeholder and add the created Repeater
PlaceHolder ph = e.Item.FindControl("PlaceHolder1") as PlaceHolder;
ph.Controls.Add(nestedRepeater);
}
}