带每行模板选择的模板化数据绑定 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 .. %>代码。
总之,我希望你喜欢这篇文章,也许你也能像我一样找到这个类的用处!如果那样的话,请投票……(如果不行,也请投票)。
