本人也是接触控件开发不久,说的不对的地方欢迎指正。
分两种情况来分析:
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而已。