自定义服务器控件在回发时将属性设置为默认值

时间:2011-12-22 01:24:09

标签: asp.net custom-server-controls

通过遵循旨在创建视频播放器的教程,我将其改编为构建HTML5范围控件。这是我的代码:

namespace CustomServerControls
{
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:Range runat=server ID=Range1></{0}:Range>")]
    public class Range : WebControl
    {
        public int Min { get; set; }
        public int Max { get; set; }
        public int Step { get; set; }
        public int Value { get; set; }

        protected override void RenderContents(HtmlTextWriter output)
        {
            output.AddAttribute(HtmlTextWriterAttribute.Id, this.ID);
            output.AddAttribute(HtmlTextWriterAttribute.Width, this.Width.ToString());
            output.AddAttribute(HtmlTextWriterAttribute.Height, this.Height.ToString());

            if (Min > Max)
                throw new ArgumentOutOfRangeException("Min", "The Min value cannot be greater than the Max Value.");

            if (Value > Max || Value < Min)
                throw new ArgumentOutOfRangeException("Value", Value,
                                                      "The Value attribute can not be less than the Min value or greater than the Max value");

            if (Min != 0)
                output.AddAttribute("min", Min.ToString());

            if (Max != 0)
                output.AddAttribute("max", Max.ToString());

            if (Step != 0)
                output.AddAttribute("step", Step.ToString());

            output.AddAttribute("value", Value.ToString());

            output.AddAttribute("type", "range");
            output.RenderBeginTag("input");
            output.RenderEndTag();

            base.RenderContents(output);
        }
    }  
}

正如您所看到的,非常简单,只要能够设置各个属性,它就可以工作。

如果我回发检查当前值是什么,控件会将其属性重置为默认值(0)。我想这是一个viewstate问题。有没有人看到上述代码中遗漏的任何内容才能使其正常工作?

修改

我注意到这个标记正在呈现给页面:

<span id="Range1" style="display:inline-block;">
    <input id="Range1" min="1" max="100" value="5" type="range">
</span>

这显然是错误的,我不想创建span标记,输入控件也没有名称。因此,当我回发时,我没有从控件中获取数据。

2 个答案:

答案 0 :(得分:1)

试试这个:

[DefaultProperty("Value")]
[ToolboxData("<{0}:Range runat=server />")]
public class Range : WebControl, IPostBackDataHandler {

    private static readonly object mChangeEvent = new object();

    public Range() : base(HtmlTextWriterTag.Input) { }

    [Category("Events")]
    public event EventHandler Change {
        add { Events.AddHandler(mChangeEvent, value); }
        remove { Events.RemoveHandler(mChangeEvent, value); }
    }

    [DefaultValue(0)]
    public int Value {
        get { return (int?)ViewState["Value"] ?? 0; }
        set { ViewState["Value"] = value; }
    }

    protected override void AddAttributesToRender(HtmlTextWriter writer) {
        base.AddAttributesToRender(writer);

        // Add other attributes (Max, Step and value)

        writer.AddAttribute(HtmlTextWriterAttribute.Value, Value.ToString());
        writer.AddAttribute(HtmlTextWriterAttribute.Name, UniqueID);
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "range");
    }

    protected virtual void OnChange(EventArgs e) {
        if (e == null) {
            throw new ArgumentNullException("e");
        }

        EventHandler handler = Events[mChangeEvent] as EventHandler;

        if (handler != null) {
            handler(this, e);
        }
    }

    #region [ IPostBackDataHandler Members ]

    public bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
        int val;

        if (int.TryParse(postCollection[postDataKey], out val) && val != Value) {
            Value = val;
            return true;
        }

        return false;
    }

    public void RaisePostDataChangedEvent() {
        OnChange(EventArgs.Empty);
    }

    #endregion

}

至于@VinayC说你必须使用ViewState和ControlState(对于关键数据)来保持控件的状态。此外,您必须实现IPostBackDataHandler以恢复最后一个值并引发更改事件,如上例所示。

<强>更新

[DefaultProperty("Value")]
[ToolboxData("<{0}:Range runat=server />")]
public class Range : WebControl, IPostBackEventHandler, IPostBackDataHandler {

    [Category("Behavior")]
    [DefaultValue(false)]
    public bool AutoPostBack {
        get { return (bool?)ViewState["AutoPostBack"] ?? false; }
        set { ViewState["AutoPostBack"] = value; }
    }

    protected override void OnPreRender(EventArgs e) {
        base.OnPreRender(e);

        if (!DesignMode && AutoPostBack) {

            string script = @"
var r = document.getElementById('{0}');

r.addEventListener('mousedown', function (e) {{
    this.oldValue = this.value;
}});

r.addEventListener('mouseup', function (e) {{
    if (this.oldValue !== this.value) {{
        {1};
    }}
}});";

            Page.ClientScript.RegisterStartupScript(
                this.GetType(),
                this.UniqueID,
                string.Format(script, this.ClientID, Page.ClientScript.GetPostBackEventReference(new PostBackOptions(this))),
                true);
        }
    }

    #region [ IPostBackEventHandler Members ]

    public void RaisePostBackEvent(string eventArgument) {
        // If you need to do somthing on postback, derive your control 
        // from IPostBackEventHandler interface.
    }

    #endregion

}

上面的代码说明了如何使用ClientScriptManager.GetPostBackEventReferenceIPostBackEventHandler为Range控件实现简单的AutoPostback。

答案 1 :(得分:0)

问题是您的属性(MinMaxStepValue)由实例字段支持 - 在ASP.NET中,每个页面和每个控件实例当页面回发时,它会被重新创建。因此,每次这些属性都将具有默认值(或您可以在设计时设置的值) - 为了在回发后保留这些值,您需要使用视图状态来备份属性。例如:

public int Min 
{ 
   get
   {
       var value = ViewState["Min"];
       return null != value ? (int)value : 0;
   }
   set { ViewState["Min"] = value; }
}

由于可以禁用视图状态,因此控件作者也可以使用控件状态来备份重要属性(这对于控件工作至关重要) - 对于使用控件状态,您需要覆盖{ {3}}和LoadControlState方法。

例如(使用控制状态):

public int Min { get; set; }
public int Max { get; set; }
public int Step { get; set; }
public int Value { get; set; }

protected override void OnInit(EventArgs e) 
{
    Page.RegisterRequiresControlState(this); // must for using control state
    base.OnInit(e);
}

protected override object SaveControlState() 
{
    return new int[] { Min, Max, Step, Value };
}

protected override void LoadControlState(object state) 
{
    var values = state as int[];
    if (null != values) 
    {
       Min = values[0]; Max = values[1];
       Step = values[2]; Value = values[3];
    }
}