65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (63投票s)

2004 年 9 月 9 日

4分钟阅读

viewsIcon

328430

downloadIcon

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中所述的那样。

这个问题可以分为三个部分:

  1. 如何定义和解析不同的ObjectTemplate
  2. 如何动态确定要使用的模板。
  3. 如何使Repeater将其状态保存在ViewState中,以便postback按预期工作。

问题 #1

为了能够动态添加对象,我重写了AddParsedSubObject()并实现了一个ControlBuilderControlBuilderAddParsedSubObject()协同工作,将定义的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值并将其用作替代。

最终注释

我在网上搜索了更多关于这个特定主题的内容,但关于添加动态模板的内容很少或几乎没有。大多数人似乎都卡在一个更简单的问题上,即动态选择整个DataGridRepeater的模板,而不是按行进行。就个人而言,我认为这种Repeater对象有很大的潜力,特别是当你重复处理可能(或不可能)是彼此子类的类的集合时。你最大的优势是不必在服务器控件(.cs类)中编写所有变体,或者在模板定义中编写大量的<% if .. %>代码。

总之,我希望你喜欢这篇文章,也许你也能像我一样找到这个类的用处!如果那样的话,请投票……(如果不行,也请投票)。

© . All rights reserved.