【问题标题】:Custom BoundField loses control on PostBack自定义 BoundField 失去对 PostBack 的控制
【发布时间】:2014-01-30 15:39:12
【问题描述】:

我已经实现了一个简单的控件,它呈现一个abbr 标签,以便使用jQuery TimeAgo 插件。

public class TimeAgoControl : System.Web.UI.WebControls.WebControl
{
    [Bindable(true)]
    [DefaultValue(null)]
    public string Iso8601Timestamp
    {
        get { return (string)ViewState["Iso8601Timestamp"]; }
        set { ViewState["Iso8601Timestamp"] = value; }
    }

    [Bindable(true)]
    [DefaultValue(null)]
    public override string ToolTip
    {
        get { return (string)ViewState["ToolTip"]; }
        set { ViewState["ToolTip"] = value; }
    }

    public override void RenderControl(HtmlTextWriter writer)
    {
        writer.AddAttribute("class", "timeago");
        writer.AddAttribute("title", Iso8601Timestamp);
        writer.RenderBeginTag("abbr");
        RenderContents(writer);
        writer.RenderEndTag();
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.WriteEncodedText(ToolTip);
    }
}

这很好用。我可以在GridView TemplateField 中使用上述控件,而且效果也很好:

<asp:TemplateField>
    <ItemTemplate>
        <my:TimeAgoControl runat="server" Iso8601Timestamp='<%# Item.LastImportDate.ToString("s") %>' 
                           ToolTip='<%# Item.LastImportDate.ToString("f") %>' />
    </ItemTemplate>
</asp:TemplateField>

当然,我不想每次我想在 GridView 中使用控件时都输入以上所有内容,所以我想我会将上述逻辑抽象为自定义 BoundField,我只需在其中传递DataField 的名称,其中包含要呈现的 DateTime 值。很简单,从System.Web.UI.WebControls.BoundField 扩展并覆盖InitializeDataCell 方法,将控件添加到其中的DataControlFieldCell,并将处理程序附加到DataBinding 事件。它可以工作并生成正确的标记:

<td><abbr class="timeago" title="Wednesday, January 29, 2014 16:17">about 23 hours ago</abbr></td>

但是当页面执行 PostBack 时,控件消失了,我只剩下

<td>29.01.2014 16:17:17</td>

请注意,该页面不使用 Ajax 功能:它不包含 UpdatePanel 或 ScriptManager。

我已经研究了很多,发现 this unanswered question 以及这个 other question 指出必须覆盖 ExtractValuesFromCell 方法,在我的情况下从未调用过。这是我的实现

public class TimeAgoBoundField : System.Web.UI.WebControls.BoundField
{
    protected override void InitializeDataCell(System.Web.UI.WebControls.DataControlFieldCell cell, System.Web.UI.WebControls.DataControlRowState rowState)
    {
        base.InitializeDataCell(cell, rowState);

        cell.Controls.Add(new TimeAgoControl());

        cell.DataBinding += (sender, e) =>
        {
            var c = (DataControlFieldCell)sender;
            //how do I get the TimeAgoControl within this scope? c.Controls.Count is 0
            var dateTimeValue = (DateTime?)DataBinder.GetPropertyValue(DataBinder.GetDataItem(c.NamingContainer), this.DataField);
            c.Controls.Add(new TimeAgoControl
            {
                Iso8601Timestamp = dateTimeValue.HasValue ? dateTimeValue.Value.ToLocalTime().ToString("s") : this.NullDisplayText,
                ToolTip = DateTimeValue.HasValue ? dateTimeValue.Value.ToLocalTime().ToString("f") : this.NullDisplayText
            });                
        };
    }
}

请注意,如果我不是在DataBind 事件处理程序中添加控件,而是在InitializeDataCell 本身中添加控件,则Controls 集合为(因此,给控件一个 ID 并尝试使用 FindControl 失败返回 null)。显然DataBind 事件不会在回发时调用,但鉴于ViewState 在生命周期的这个阶段处于活动状态,我本来希望控件在回发时保持不变。

任何指针将不胜感激。提前致谢。

【问题讨论】:

    标签: c# asp.net data-binding webforms asp.net-4.5


    【解决方案1】:

    有点过时了,但如果这对某人有帮助,我通过启动反编译器 (DotPeek) 并分析调用 InitializeDataCell 时基本控件在做什么来解决这个问题。事实证明,这个方法是protected,而是在一些初始化之后从public 方法InitializeCell 依次调用。此外,它还负责将OnDataBindField 方法订阅到DataBinding 事件。对OnDataBindField 的进一步检查显示,此方法负责设置TableCellText 属性,因此为什么在回发之后,我只剩下日期的字符串表示形式。

    我将 Initialize 方法更改为 InitializeCell 并覆盖 OnDataBindField(不调用其基本对应项),如下所示:

    public class TimeAgoBoundField : System.Web.UI.WebControls.BoundField
    {
        public override bool ReadOnly
        {
            get { return true; }
        }
    
        public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
        {
            base.InitializeCell(cell, cellType, rowState, rowIndex);
    
            if (cellType == DataControlCellType.DataCell)
            {
                cell.Controls.Add(new TimeAgoControl());
            }
        }
    
        protected override void OnDataBindField(object sender, EventArgs e)
        {
            if (sender is TableCell)
            {
                var cell = (TableCell)sender;
                var cellValue = this.GetValue(cell.NamingContainer);
    
                if (cellValue != null)
                {
                    var timeAgoControl = (TimeAgoControl) cell.Controls[0];
                    var dateTimeValue = (DateTime) cellValue;
                    var utcDateTime = dateTimeValue.Kind != DateTimeKind.Utc ? dateTimeValue.ToUniversalTime() : dateTimeValue;
                    timeAgoControl.ISO8601Timestamp = utcDateTime.ToString("s") + "Z";
                }
                else if (this.NullDisplayText != null)
                {
                    cell.Text = this.NullDisplayText;
                }
            }
        }
    }
    

    BoundField.OnDataBindFieldmsdn documentation 现在包含此注释:

    继承人须知 扩展 BoundField 类时,您可以重写此方法以执行自定义绑定例程。

    【讨论】:

      猜你喜欢
      • 2011-08-19
      • 1970-01-01
      • 1970-01-01
      • 2010-09-09
      • 1970-01-01
      • 1970-01-01
      • 2018-06-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多