asp.net 自定义组合控件必须继承INamingContainer接口原因分析

来源:互联网 发布:广告联盟知乎 编辑:程序博客网 时间:2024/05/21 09:39

本人也是接触控件开发不久,说的不对的地方欢迎指正。

分两种情况来分析:


1、页面存在多个自定义控件

我们都知道INamingContainer接口功能就是为每一个控件生成唯一的Id,防止页面上存在多个自定义控件时控件Id重复造成的混乱。想必这种情况大家都很好理解


2、页面只存在一个自定义控件

当自定义控件没有继承INamingContainer接口时,会导致其子控件的状态丢失,子控件事件将无法触发。

我们来通过一个简单关键字搜索的例子来分析,该控件包含一个TextBox和一个Button

界面效果:


代码:


public class SelfKeyWordSearch:WebControl
    {
        private TextBox txtKeyWord;
        private Button btnSearch;
        public string Text
        {
            get
            {
                EnsureChildControls();
                return txtKeyWord.Text.Trim();
            }
        }

        protected override void CreateChildControls()
        {
            this.Controls.Clear();
            txtKeyWord = new TextBox();
            txtKeyWord.ID = "txtKeyWord";
            this.Controls.Add(txtKeyWord);
            btnSearch = new Button();
            btnSearch.ID = "btnSearch";
            btnSearch.Text = "搜索";
            btnSearch.CommandName = "SearchClick";
            this.Controls.Add(btnSearch);
            ChildControlsCreated = true;
        }
        private static object searchClick=new object();
        public event EventHandler SearchClick
        {
            add
            {
                base.Events.AddHandler(searchClick, value);
            }
            remove
            {
                base.Events.RemoveHandler(searchClick, value);
            }
        }

        protected override bool OnBubbleEvent(object source, EventArgs args)
        {
            bool flag = false;
            if(args is CommandEventArgs)
            {
                CommandEventArgs comEvent = (CommandEventArgs)args;
                if (comEvent.CommandName == "SearchClick")
                {
                    SearchEventArgs searEventArgs=new SearchEventArgs();
                    searEventArgs.KeyWord=this.Text;
                    btnSearch_Click(source, searEventArgs);
                    flag = true;
                }
                RaiseBubbleEvent(source, args);
            }
            return flag;            
        }


        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
        }

        protected void btnSearch_Click(object sender, EventArgs e)
        {
            EventHandler eventHandler =base.Events[searchClick] as EventHandler;
            if (eventHandler != null)
                eventHandler(this, e);
        }

        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Border, "0px;");
            writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0px;");
            writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0px;");
            writer.RenderBeginTag(HtmlTextWriterTag.Table);
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            txtKeyWord.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            btnSearch.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderEndTag();
            writer.RenderEndTag();
        }
    }

    public class SearchEventArgs : EventArgs
    {
        public string KeyWord
        {
            get;
            set;
        }
    }

 当点击搜索按钮时,输入框中的文本回发后丢失,且不触发页面中自定义的Click事件.

我们先来分析输入框文件丢失:

控件内的子控件的状态不是自己维护的,而是交给子控件自己去维护。既然是TextBox,那我们看看它到底是如何恢复状态的,通过反编译代码如下:

protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection){    base.ValidateEvent(postDataKey);    string text = this.Text;    string str2 = postCollection[postDataKey];    if (!this.ReadOnly && !text.Equals(str2, StringComparison.Ordinal))    {        this.Text = str2;        return true;    }    return false;}

那就是说TextBox自己的LoadPostData方法没执行,否则就能还原状态了,为什么没有执行呢?我们知道所有的控件都在页面page类中,其所有行为都由Page类控制触发,Page类的入口函数为:ProcessRequest,然后执行ProcessRequestMain,关键就在这个函数。这个函数中有一段代码:

if (this.IsPostBack)

{

if (context.TraceIsEnabled)

{

this.Trace.Write("aspx.page", "Begin ProcessPostData Second Try");

}

this.ProcessPostData(this._leftoverPostData,false);

...

}


再来看看ProcessPostData实现

private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad)
{
    if (this._changedPostDataConsumers == null)
    {
        this._changedPostDataConsumers = new ArrayList();
    }
    if (postData != null)
    {
        foreach (string str in postData)
        {
            if ((str == null) || IsSystemPostField(str))
            {
                continue;
            }
            Control control = this.FindControl(str);
            if (control == null)
            {
                if (fBeforeLoad)
                {
                    if (this._leftoverPostData == null)
                    {
                        this._leftoverPostData = new NameValueCollection();
                    }
                    this._leftoverPostData.Add(str, null);
                }
                continue;
            }
           IPostBackDataHandler postBackDataHandler = control.PostBackDataHandler;
            if (postBackDataHandler == null)
            {
                if (control.PostBackEventHandler != null)
                {
                    this.RegisterRequiresRaiseEvent(control.PostBackEventHandler);
                }
            }
            else
            {
                if (postBackDataHandler != null)
                {
                    NameValueCollection postCollection = control.CalculateEffectiveValidateRequest() ? this._requestValueCollection : this._unvalidatedRequestValueCollection;
                    if (postBackDataHandler.LoadPostData(str, postCollection))
                    {
                        this._changedPostDataConsumers.Add(control);
                    }
                }
                if (this._controlsRequiringPostBack != null)
                {
                    this._controlsRequiringPostBack.Remove(str);
                }
            }

        }
    }
    ......
}

可以看出 Control control = this.FindControl(str);为空时则不会执行控件的LoadPostData,当然控件的状态无法恢复。问题又来了,为什么this.FindControl(str);方法返回null?

我们继续来看Page类的FindControl方法实现,实际上调用的是基类Control:

protected virtual Control FindControl(string id, int pathOffset)
{
    string str;
    RuntimeHelpers.EnsureSufficientExecutionStack();
    this.EnsureChildControls();
    if (!this.flags[0x80])
    {
        Control namingContainer = this.NamingContainer;
        if (namingContainer != null)
        {
            return namingContainer.FindControl(id, pathOffset);
        }
        return null;
    }
    if (this.HasControls())
    {
        this.EnsureOccasionalFields();
        if (this._occasionalFields.NamedControls == null)
        {
            this.EnsureNamedControlsTable();
        }
    }
    if ((this._occasionalFields == null) || (this._occasionalFields.NamedControls == null))
    {
        return null;
    }
    char[] anyOf = new char[] { '$', ':' };
    int num = id.IndexOfAny(anyOf, pathOffset);
    if (num == -1)
    {
        str = id.Substring(pathOffset);
        return (this._occasionalFields.NamedControls[str] as Control);
    }
    str = id.Substring(pathOffset, num - pathOffset);
    Control control2 = this._occasionalFields.NamedControls[str] as Control;
    if (control2 == null)
    {
        return null;
    }
    return control2.FindControl(id, num + 1);
}

这段代码其实看的不是很明白,这里我们做一个验证,在页面cs文件中执行一段Control control=this.FindControl('txtKeyWord');即查找上面我们例子中的TextBox,发现返回的为空。
FindControl参数Id实际为控件的UniqueId,如果有父控件,则其组成形式为:父控件Id+$+子控件Id,当组合控件没有继承INamingContainer时,其UniqueId即为其Id,Page页面中自然找不到该控件。

总结:

当组合控件没有继承INamingContainer接口时,运用程序无法为控件生成控件层次,导致控制行为的Page类无法找到该控件,从而导致该控件无法完成正常的生命周期,不能正常工作。

关于子控件事件不执行其实道理同上,只不过没有执行RaisePostBackEvent而已。


0 0
原创粉丝点击