最近手头有个活儿,需要展示一个列表,列表里头,每一项又包含一个小列表。比如说,显示一些分类,然后每个分类下面再列出属于它的小项。这种一层套一层的结构,第一时间我就想到用数据绑定控件来做,能省不少事。
一开始琢磨用哪个控件。像 GridView 这种,虽然功能强,但感觉生成出来的 HTML 结构太固定,都是 table 套 table,有时候为调样式,得写好多 CSS 去覆盖它默认的,挺麻烦。想着 Repeater 这玩意儿它生成的 HTML 干净,基本上就是你模板里写它就生成外面不给你乱加东西,自由度高多。决定就用 Repeater 来搞。
开始动手和遇到的坎
思路是这样的:外面用一个 Repeater 显示主列表(比如分类),然后在主 Repeater 的项模板(ItemTemplate)里面,再放一个 Repeater,用来显示子列表(比如分类下的小项)。
想法挺撸起袖子就干。先把外层的 Repeater 拖到页面上,设置好数据源,绑定,显示出来,没问题。在它的 `
然后问题就来。外层 Repeater 每一行的数据是有,但内层那个 Repeater 的数据咋办?我当时有点懵,试着在页面加载(Page_Load)的时候给内层 Repeater 找数据源,但发现根本找不到这个内层控件,因为它是在外层 Repeater 的模板里,还没生成。直接在 ASPX 页面里给内层 Repeater 设置 DataSourceID 也不对劲,因为内层的数据是跟外层当前是哪一项挂钩的,不是一个固定的数据源。
捣鼓半天,内层列表就是出不来数据。要么报错,要么就是空的。上网搜搜,也看到不少人问类似的问题,有些答案试下,感觉不太对,或者写得太复杂,看得云里雾里。
找到窍门:关键在事件处理
后来静下心来又琢磨琢磨,觉得关键点应该在于:内层 Repeater 的数据绑定,必须发生在外层 Repeater 处理每一项数据的时候。也就是说,当外层 Repeater 生成第一行时,我得告诉它对应的内层 Repeater 该用哪些数据;生成第二行时,再告诉第二行里的内层 Repeater 用另外的数据。
顺着这个思路,我想到 Repeater 有个 `ItemDataBound` 事件。这个事件,在外层 Repeater 绑定数据、创建每一项(包括 Header, Footer, Item, AlternatingItem 等)的时候都会触发。这不正好吗?
我赶紧试试这个路子:
- 给外层的 Repeater 添加 `OnItemDataBound` 事件处理器。就是在 ASPX 页面的 Repeater 标签上加上 `OnItemDataBound="OuterRepeater_ItemDataBound"`,然后在后台代码(.cs 文件)里写这个 `OuterRepeater_ItemDataBound` 方法。
- 在这个方法里面,得判断当前处理的是不是数据项。因为 HeaderTemplate, FooterTemplate 也会触发这个事件,但我们只关心包含内层 Repeater 的数据项。加上判断:`if (* == * * == *)`。
- 就在这个 `if` 块里头,通过 `*("InnerRepeaterID")` 来找到当前项里面的那个内层 Repeater 控件。注意,`"InnerRepeaterID"` 是你在 ASPX 里给内层 Repeater 设置的 ID。
- 找到内层 Repeater 控件后,下一步就是给它找数据。外层 Repeater 当前项的数据可以通过 `*` 拿到。这个 DataItem 通常就是你绑定给外层 Repeater 的数据源里的一个对象。
- 假设这个对象里有个属性(比如叫 `SubItems`)是一个列表,正好是内层 Repeater 需要显示的数据。那我就从 `*` 里把这个 `SubItems` 列表取出来。
- 一步,把取出来的数据(`SubItems` 列表)设置给内层 Repeater 的 `DataSource` 属性,然后调用内层 Repeater 的 `DataBind()` 方法。
代码大概就是这个逻辑:
if (* == * * == *) {
// 找到内层 Repeater
Repeater innerRepeater = (Repeater)*("InnerRepeaterID");
// 获取外层当前项的数据
var currentOuterItemData = *;
// 从外层数据中提取内层需要的数据 (假设有个 SubItems 属性)
var innerDataSource = GetSubItemsFromOuterData(currentOuterItemData); // 这里你需要根据实际情况写获取子数据的方法
// 绑定内层 Repeater
if (innerRepeater != null && innerDataSource != null) {
* = innerDataSource;
*();
}
}
void OuterRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e) {
结果和感受
这么一改,再运行页面,成!外层列表正常显示,每个外层项下面,对应的内层小列表也规规矩矩地显示出来,数据完全正确。
感觉这个方法逻辑很清晰:外层管好自己的一亩三分地,当轮到生成每一项时,再负责把自己这一项里的内层 Repeater 给初始化好、数据绑定各司其职,不乱套。
而且最终生成的 HTML 也很干净,没有像 GridView 那样多余的 table 结构,调整样式方便多。这回实践下来,对 Repeater 的灵活和 `ItemDataBound` 事件的重要性有更深的体会。以后再遇到类似需要嵌套展示数据的场景,这个方法肯定能派上大用场。