带每行模板选择的模板化数据绑定 Repeater 控件






4.89/5 (63投票s)
2004 年 9 月 9 日
4分钟阅读

328430

2910
此控件允许您根据Repeater中每一行的内容,为不同的行选择不同的模板。
引言
本周我偶然遇到了一个问题。我需要显示订单的详细信息。通常,我只需要一个repeater
,然后就完成了。但这次有些不同。订单行集合中的某些行不是订单行。例如,我的集合可能包含3个OrderRows
和1个OrderDescription
行。
我必须生成类似这样的输出
土豆 | $12,99 | (移除) |
卷心菜 | $10,00 | (移除) |
(请注意,卷心菜是切片运输的) | ||
啤酒箱 | $4,12 | (移除) |
我无意讨论这个例子的有用性或内容。关键在于,使用通用的Repeater
很难做到这一点。
预期解决方案
我想能够动态决定为我集合中的每个元素使用哪个模板。这要求我能在我的.asp代码中描述多个模板。
<cc:MyRepeater id="order" runat="server">
<ItemTemplate forClass="OrderRow">
<tr>
<td><%# DataBinder.Eval(Container.DataItem, "Name")%></td>
<td><%# DataBinder.Eval(Container.DataItem, "Price", "C")%></td>
<td><asp:Button id="Remove" CommandName="remove" runat="server"/></td>
</tr>
</ItemTemplate>
<ItemTemplate forClass="OrderDescription">
<tr>
<td colspan="3"><%# DataBinder.Eval(Container.DataItem, "Name")%></td>
</tr>
</ItemTemplate>
</cc:MyRepeater>
在我的研究过程中,我遇到了一些看起来不可能实现的棘手问题。然而,经过一番繁琐的搜索,我找到了解决方案。
解决方案
我必须对上述模型进行一些小的改动。不仅无法为不同的ItemTemplate
添加属性,也无法动态定义ITemplate
属性。我通过将不同的模板包装到子对象中来解决了这个问题,如下所示:
<cc:ObjectRepeater id="order" runat="server">
<ObjectTemplate name="orderRow">
<ItemTemplate>
<tr>
<td><%# DataBinder.Eval(Container.DataItem, "Name")%></td>
<td><%# DataBinder.Eval(Container.DataItem, "Price", "C")%></td>
<td><asp:Button id="Remove" CommandName="remove" runat="server"/></td>
</tr>
</ItemTemplate>
</ObjectTemplate>
<ObjectTemplate name="description">
<ItemTemplate>
<tr>
<td colspan="3"><%# DataBinder.Eval(Container.DataItem, "Name")%></td>
</tr>
</ItemTemplate>
</ObjectTemplate>
</cc:ObjectRepeater>
我还为我的ObjectRepeater
添加了一个委托,该委托允许我确定选择哪个模板。这个委托可以在你的代码隐藏中分配。该委托必须返回当前数据绑定项要使用的模板的名称。例如:
private void Page_Load(object sender, System.EventArgs e)
{
order.DetermineTemplate =
new ObjectRepeaterDetermineTemplateDelegate(this.determineTemplate);
}
public string determineTemplate(object sender, object dataItem)
{
if(dataItem is OrderRow)
return "orderRow";
else
return "description";
}
此委托还可以用于检查DataItem
的某些属性,而不仅仅是检查类类型。
实现细节
为了实现这个解决方案,我必须开发一个模板化的数据绑定自定义控件,正如MSDN中所述的那样。
这个问题可以分为三个部分:
- 如何定义和解析不同的
ObjectTemplate
。 - 如何动态确定要使用的模板。
- 如何使
Repeater
将其状态保存在ViewState中,以便postback按预期工作。
问题 #1
为了能够动态添加对象,我重写了AddParsedSubObject()
并实现了一个ControlBuilder
。ControlBuilder
和AddParsedSubObject()
协同工作,将定义的ObjectTemplate
保存在一个名为_templates
的私有集合中。
请查看附件的项目以获取详细信息,因为这并不是什么高深的技术。
技巧在于向ObjectTemplate
类添加一个ITemplate
属性,并为该属性加上[TemplateContainer(typeof(RepeaterItem))]
属性。由于ASP解析器检测到我的ObjectTemplate
也是一个控件,它会将该对象很好地转换为一个模板。
问题 #2
在CreateItem
的代码中,有一个地方会创建一个子行。
private RepeaterItem CreateItem(int itemIndex,
ListItemType itemType, bool dataBind, object dataItem)
{
RepeaterItem item = new RepeaterItem(itemIndex, itemType);
RepeaterItemEventArgs e = new RepeaterItemEventArgs(item);
//decide which template to use.
string templateName = null;
if(dataBind)
{
if(DetermineTemplate != null)
{
templateName = this.DetermineTemplate(this, dataItem);
ViewState["templateName" + itemIndex.ToString()] = templateName;
}
}
else
{
//determine template to use from viewState;
templateName = (string)ViewState["templateName" + itemIndex.ToString()];
}
if(templateName == null)
templateName = this.DefaultTemplate;
CustomDynamicTemplate dynamicTemplate =
(CustomDynamicTemplate)_templates[templateName];
//Must exist.
dynamicTemplate.ItemTemplate.InstantiateIn(item);
if (dataBind)
{
item.DataItem = dataItem;
}
OnItemCreated(e);
this.Controls.Add(item);
if (dataBind)
{
item.DataBind();
OnItemDataBound(e);
item.DataItem = null;
}
return item;
}
对于每一行,我调用一个委托(你的委托)来确定模板的名称。然后我在我的HashTable
模板库中查找它,并执行InstantiateIn()
。其余的代码与微软关于数据绑定模板化自定义控件的示例非常相似。
问题3
最终的问题在于,所使用的模板(每行)必须保存在ViewState中,否则postback事件将不会触发,或者更糟的是,会为错误的行触发。
在上面的代码中,我维护了ViewState
属性,以便记住每行的已使用模板。在postback之后,将检索此ViewState
值并将其用作替代。
最终注释
我在网上搜索了更多关于这个特定主题的内容,但关于添加动态模板的内容很少或几乎没有。大多数人似乎都卡在一个更简单的问题上,即动态选择整个DataGrid
或Repeater
的模板,而不是按行进行。就个人而言,我认为这种Repeater
对象有很大的潜力,特别是当你重复处理可能(或不可能)是彼此子类的类的集合时。你最大的优势是不必在服务器控件(.cs类)中编写所有变体,或者在模板定义中编写大量的<% if .. %>
代码。
总之,我希望你喜欢这篇文章,也许你也能像我一样找到这个类的用处!如果那样的话,请投票……(如果不行,也请投票)。