要想熟练的开发服务器控件 首先需要了解asp.net 中服务器控件的生命周期。其实服务器控件的生命周期与asp.net 页面的生命周期差不多, 因为asp.net 页面其实就是间接从Control继承。 #region if (namingContainer != null && c.InitRecursive(namingContainer); initing = true; // savedInfo.First 保存控件本身属性的视图状态 ArrayList controlList = savedInfo.Second as ArrayList; pendingVS[k] = controlStates[i]; viewStateLoaded = true; 6 Load #region ArrayList controlList = null; int idx = -1; if (controlList == null) controlList.Add(idx); return new Triplet(thisState, controlList, controlStates); protected virtual void RenderChildren(HtmlTextWriter writer) //DIT 来源:http://www.cnblogs.com/wwwyfjp
我们来看每个阶段Control控件里面都做了一些什么事情
1. Instantiate :
控件被页面或者另一控件调用, 实例化。
2. Initialize :
3. Begin Tracking View State :
\n
internal void InitRecursive(Control namingContainer)
{
if (_controls != null)
{
if (_isNamingContainer) // _isNamingContainer 指示控件是否从实现INamingContainer 接口
namingContainer = this;
\n
namingContainer._userId == null &&
namingContainer.autoID)
namingContainer._userId = namingContainer.GetDefaultName() + “b”;
// 以下循环递归调用InitRecursive 初始化控件树中的所有控件。所做的工作主要有:
foreach (Control c in _controls)
{
c._page = Page; // 将父控件的 page 对象 赋给所有子控件
c._namingContainer = namingContainer; // 往子控件传递 NamingContainer.
if (namingContainer != null && c._userId == null && c.autoID)
c._userId = namingContainer.GetDefaultName() + “c”; // 根据NamingContainer 给控件的userId 赋值, 我们经常使用的
// UniqueID, ClientID 都与这个userId 相关联
\n
}
}
\n
OnInit(EventArgs.Empty); // 激发OnInit 事件。
TrackViewState(); // 该方法的内部实现其实就是设置一个标识 指示开始 Track ViewState 状态。
inited = true;
initing = false;
}
#Endregion ** 从代码中我们可以看出OnInit 事件中给控件赋值并不会被保存到ViewState 中去。
4 Load View State :
在这一阶段控件通过 LoadViewStateRecursive 方法从ViewState 中还原状态。
#region
internal void LoadViewStateRecursive(object savedState)
{
if (!EnableViewState || savedState == null)
return;
\n
// savedInfo.Third[i] 保存第i 个控件的视图状态
// savedInfo.Second[i] 保存当前需要保存视图状态的控件列表, 如果控件已经被移出(比如说调用了Controls.Clear())
//其视图状态被保存到pendingVS 这个Hashtable 中 中
Triplet savedInfo = (Triplet)savedState;
LoadViewState(savedInfo.First);
\n
if (controlList == null)
return;
ArrayList controlStates = savedInfo.Third as ArrayList;
int nControls = controlList.Count;
// 递归调用 为每一个子控件加载视图状态
for (int i = 0; i < nControls; i++)
{
int k = (int)controlList[i];
if (k < Controls.Count && controlStates != null)
{
Control c = Controls[k];
c.LoadViewStateRecursive(controlStates[i]);
}
else
{
if (pendingVS == null)
pendingVS = new Hashtable();
\n
}
}
\n
}
#endregion
5 Load Postback Data
对于需要加载回发数据的控件必须实现IPostBackDataHandler 接口
public interface IPostBackDataHandler
{
bool LoadPostData(string postDataKey, NameValueCollection postCollection);
void RaisePostDataChangedEvent();
}
在这一阶段 控件调用LoadPostData 接口方法来获取回发数据, 其中的postDataKey 表明回发数据的关键字 ,
NameValueCollection 是所有PostBack 的数据的集合。
返回值指示是否 激发 RaisePostDataChangedEvent 事件。
\n
这一阶段比较简单, 也是我们最常用的。
\n
#region
internal void LoadRecursive()
{
OnLoad(EventArgs.Empty); // 激发OnLoad事件
if (_controls != null)
{
foreach (Control c in _controls)
c.LoadRecursive(); // Load 子控件
}
loaded = true;
}
#endregion
7 Raise Change Event
判断Load Postback Data 阶段的返回值, 如果返回True,就执行RaisePostDataChangedEvent()方法.
8 Raise PostBack Event
这一阶段只有单控件实现 IPostBackEventHandler 接口时执行。
void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)。 该接口方法主要用来允许
开发者捕获PostBack 事件并执行自定义逻辑。 不好理解, 看看Button 控件的实现就明白了。
\n
#region
void IPostBackEventHandler.RaisePostBackEvent (string eventArgument)
{
RaisePostBackEvent (eventArgument);
}
protected virtual void RaisePostBackEvent (string eventArgument)
{
// 验证控件
if (CausesValidation)
Page.Validate (ValidationGroup);
// 激发onClick 事件, 执行用户onclick 事件代码。
onClick (EventArgs.Empty);
// 激发OnCommand 事件, 执行用户OnCommand 自定义代码。
OnCommand (new CommandEventArgs (CommandName, CommandArgument));
}
#endregion
9 PreRender
\n
internal void PreRenderRecursiveInternal()
{
if (_visible)
{
EnsureChildControls(); // 检查子控件是否创建, 如果未创建,侧创建。
OnPreRender(EventArgs.Empty); // 激发OnPreRender 事件
if (_controls == null)
return;
// 递归调用子控件上的PreRenderRecursiveInternal()
foreach (Control c in _controls)
c.PreRenderRecursiveInternal();
}
prerendered = true;
}
// 保障子控件不会被重复创建
protected virtual void EnsureChildControls()
{
if (ChildControlsCreated == false && !creatingControls)
{
creatingControls = true;
CreateChildControls();
ChildControlsCreated = true;
creatingControls = false;
}
}
#endregion
看到这段代码之前也写过一些组合控件, 依样画葫芦 重写 ChildControlsCreated(), 一直以为
ChildControlsCreated()在控件生命周期的特定阶段被调用, 现在才知道以前的想法是错的。
基本上ChildControlsCreated() 是在第一次调用EnsureChildControls 的时候被创建,最晚是在Page Load
之后, PreRender 之前被调用, Control 类中另一个调用EnsureChildControls 的地方是在FindControl()
方法里面。 也就 是说一旦你调用了FindControl() 方法, 子控件就已经被自动创建。
10 SaveViewState
保存视图状态, 与Load ViewState 刚好相反。
\n
#region
internal object SaveViewStateRecursive()
{
if (!EnableViewState)
return null;
\n
ArrayList controlStates = null;
\n
// 保存子控件视图状态
foreach (Control ctrl in Controls)
{
object ctrlState = ctrl.SaveViewStateRecursive();
idx++;
if (ctrlState == null)
continue;
\n
{
controlList = new ArrayList();
controlStates = new ArrayList();
}
\n
controlStates.Add(ctrlState);
}
// 保存控件本身视图状态
object thisState = SaveViewState();
if (thisState == null && controlList == null && controlStates == null)
return null;
\n
}
#endregion
11 Render
Render 阶段
\n
#region
public void RenderControl(HtmlTextWriter writer)
{
// 控件是否可见
if (_visible)
Render(writer);
}
protected virtual void Render(HtmlTextWriter writer) //DIT
{
RenderChildren(writer);
}
\n
{
// SetRenderMethodDelegate 可以设置一个Render代理。
if (_renderMethodDelegate != null)
_renderMethodDelegate(writer, this);
else if (_controls != null)
// 递归调用, Render 子控件
foreach (Control c in _controls)
c.RenderControl(writer);
}
#endregion
看到这三个方法, 终于搞清楚了RenderControl, Render, RenderChildren 三个方法的区别。 这三个方法的设计 充分体现了通过组合, 而不是通过过程来实现的面向对象原则。
12 UnLoad
13 Dispose
这2 个阶段大同小异, 都是通过递归调用来卸载页面 释放资源。
我的下一篇文章将分析WebControl 是如何从Control 继承, 并实现自定义逻辑。
\n