在我的第一篇文章讨论控件生命周期的时候。提到控件生命周期中的LoadViewState, LoadPostBackData, RaiseChangedEvents
RaisePostbackEvent 等几个阶段是页面在postback 中所特有的。深刻得理解控件的postback 机制是开发专业的 自定义控件所必须的基本功。
1. 如何引发一个Postback. Asp.nt 内置有2 种方法可以引起页面的postback。
1> HTML 元素 <input type="submit"> 和 <input type="image"> 在 客户端点击的时候将引发postback, asp.net 内置了对于这2 个标签的支持,
不需要我们做任何事情, 只需要将按钮的type 注册为Image 或者是 submit . 那么在点击按钮的时候, 按钮就自动引发一个postback.
2> 其他的html标签 比如说link, 或者一个 文本框默认是不会引发postback 的,那么怎么办呢?别着急, asp.net 为我们提供了另外一个机制来引发postback.
那就是__doPostBack 函数。具体怎么做呢?我以我上一篇回复里面的PostBackTest 控件为例来说明
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public class PostBackTest : WebControl
{
protected override HtmlTextWriterTag TagKey
{
get
{
return HtmlTextWriterTag.Div;
}
}
public string Argument
{
get
{
object obj = ViewState["argument"];
return obj==null?string.Empty: Convert.ToString(obj);
}
set { ViewState["argument"] = value; }
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
// 获取客户端PostBack 引用。
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, this.Page.ClientScript.GetPostBackEventReference(this, Argument, false));
base.AddAttributesToRender(writer);
}
}
核心的代码就是这一句。
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, this.Page.ClientScript.GetPostBackEventReference(this, Argument, false));
它通过GetPostBackEventReference 方法将客户端的Onclick 事件引入到服务器控件的postback 机制中。
在测试页面中把 控件的Argument 属性值设为“arg”, 然后查看页面源代码, 看看 GetPostBackEventReference 具体做了些什么事情。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>
Untitled Page
</title></head>
<body>
<form name="form1" method="post" action="Admin.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTE4MzY2NzQwMTBkZLy7Vyj+UIgNEwe+sSAd2omK63nn" />
</div>
<div>
<div onclick="__doPostBack('PostBackTest1','arg')" id="PostBackTest1">
</div>
</div>
<div>
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
</div>
<script type="text/javascript">
<!--
var theForm = document.forms['form1'];
if (!theForm) {
theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
// -->
</script>
</form>
</body>
</html>
哇唔!! 多出来好多东东,首先映入我们眼帘的也是最重要的一句就是 <div onclick="__doPostBack('PostBackTest1','arg')" id="PostBackTest1">, 这就是我们控件所
Render 的HTML 标记。 在他的onClick 事件上调用了__doPostBack('PostBackTest1','arg')函数,我们可以看到这个函数在页面的最下方定义好了。
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
仔细阅读这个函数, 我们发现它把我们控件的UniqueID 与 自定义的参数 分别 赋值给2 个隐藏域, 然后调用theForm.submit() 提交表单, 一个完整的PostBack请求就发出了。
这一切都是GetPostBackEventReference的功劳。当然除了GetPostBackEventReference 之外。 asp.net2.0 还提供了GetPostBackClientHyperlink 来通过 一个link 的 href
属性来引发postback。 这里就不多介绍了。
2. PostBack 引发之后, 如何在服务器端获取PostBack 的数据呢?
要想在控件里面获取PostBack 数据, 需要我们的控件实现 IPostBackDataHandler 接口, 这个接口 主要实现2 个方法:
public interface IPostBackDataHandler
{
bool LoadPostData(string postDataKey, NameValueCollection postCollection);
void RaisePostDataChangedEvent();
}
其中的LoadPostData 将在控件LoadViewState 之后, Load 之前被调用。 首先我们来看一个简单的SimpleTextBox Control 在PostBack 的时候怎么样来获取PostBack 得值
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public class SimpleTextBox : Control, IPostBackDataHandler
{
private string postData = null;
public string Text
{
get
{
object obj = ViewState["Text"];
return obj == null ? string.Empty : Convert.ToString(obj);
}
set { ViewState["Text"] = value; }
}
private string postData = string.Empty;
protected override void Render(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Width, "120px");
writer.AddAttribute(HtmlTextWriterAttribute.Height, "20px");
writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
writer.AddAttribute(HtmlTextWriterAttribute.Value, Text);
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag();
writer.AddAttribute(HtmlTextWriterAttribute.Type, "submit");
writer.AddAttribute(HtmlTextWriterAttribute.Width, "40px");
writer.AddAttribute(HtmlTextWriterAttribute.Height, "20px");
writer.AddAttribute(HtmlTextWriterAttribute.Name, "submit");
writer.AddAttribute(HtmlTextWriterAttribute.Value, "提交");
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag();
}
#region IPostBackDataHandler Members
public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
postData = postCollection[postDataKey];
return false;
}
public void RaisePostDataChangedEvent()
{
// throw new Exception("The method or operation is not implemented.");
}
#endregion
}
在测试页面中使用SimpleTextBox, 并且查看源代码:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>
Untitled Page
</title></head>
<body>
<form name="form1" method="post" action="Admin.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNDAyNzk2MDg4ZGRQ1mZfgls8cf2qFnPGZ9L+uKOeQg==" />
</div>
<div>
<input width="120px" height="20px" id="SimpleTextBox1" name="SimpleTextBox1" value="" />
<input id= "submit" type="submit" width="40px" height="20px" value="提交" />
</div>
</form>
</body>
</html>
LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection) 里面的第一个参数的值是控件的UniqueID, 第2 个参数是一个
PostBackData 集合。 在我们的SimpleTextBox 控件里面, 它包含2 个键值对(__VIEWSTATE, "/wEPDwUJNDAyNzk2MDg4ZGRQ1mZfgls8cf2qFnPGZ9L+uKOeQg==")与 (SimpleTextBox1, “”)
在这里需要注意的一点就是。 在Render 控件的过程当中, 需要把控件的UniqueID 作为需要PostBackData 的Input 标签的name 属性。否则即使实现了IPostBackDataHandler 接口
LoadPostData 方法也不会执行。如果我们的控件Render 多个Input 标签, 或者没有Render 任何Input 标签, 那么怎么办呢? LoadPostData 就无法执行了吗?
当然不是了, 微软已经为我们想的很周到了, 我们只需要在控件的PreRender 方法里面注册一下就好了。
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
this.Page.RegisterRequiresPostBack(this);
}
这样的话无论PostBackData 的键集合里面是否包含控件的UniqueID, LoadPostData 方法都会照常执行。
3. 引发控件的Change 事件。 在这个阶段, 控件会首先检查LoadPostData 阶段的返回值, 如果返回"True" 则会执行 void RaisePostDataChangedEvent() 方法,
我们一般在控件在生命周期的这个阶段引发控件的Change 事件。 比如说TextBox 控件的TextChange 事件, 就是在这个时候引发的。 让我们来为我们的SimpleTextBox
控件添加OnTextChange 事件。 首先将上面的LoadPostData 方法改为返回“Ture”, 这样控件的下一步就会调用 RaisePostDataChangedEvent 方法。
public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
postData = postCollection[postDataKey];
return true;
}
然后按照上一篇所说的方法定义一个SimpleTextChange事件。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
private static readonly object EventSimpTextChange = new object();
public event EventHandler SimpTextChange
{
add
{
Events.AddHandler(EventSimpTextChange, value);
}
remove
{
Events.RemoveHandler(EventSimpTextChange, value);
}
}
public virtual void OnSimpTextChange(EventArgs e)
{
EventHandler tcHandler = (EventHandler)Events[EventSimpTextChange];
if (tcHandler != null)
{
tcHandler(this, e);
}
}
最后在RaisePostDataChangedEvent 方法中根据情况引发事件。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
public void RaisePostDataChangedEvent()
{
if (postData != Text)
{
OnSimpTextChange(EventArgs.Empty);
}
Text = postData;
}
4. IPostBackEventHandler 接口的使用。 回顾控件生命周期, 我们知到, 在控件引发Changed 事件之后, 下一个阶段就是 引发 PostBack 事件. 如果控件需要引发PostBack 事件
必须实现IPostBackEventHandler 接口, 该接口定义一个 RaisePostBackEvent 的方法。 用来 引发PostBack 事件。 还是以我们的SimpleTextBox 控件为例,如果我们需要在用户
点击”提交“ 按钮之后引发一个OnSubmit 事件。 我们该怎么做呢?
老规矩, 我们还是首先定义一个 OnSubmit 的事件
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
Code
private static readonly object EventSubmit = new object();
public event EventHandler Submit
{
add
{
Events.AddHandler(EventSubmit, value);
}
remove
{
Events.RemoveHandler(EventSubmit, value);
}
}
public virtual void OnSubmit(EventArgs e)
{
EventHandler submitHandler = (EventHandler)Events[EventSubmit];
if (submitHandler != null)
{
submitHandler(this, e);
}
}
然后在 在RaisePostBackEvent 方法里面激发事件。
void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
{
OnSubmit(EventArgs.Empty);
}
启动调试, 你会发现 RaisePostBackEvent 并不会执行。 原来与 LoadPostData 方法一样。 如果要默认执行RaisePostBackEvent 方法, 必须将控件的UniqueID 赋值给
应发事件的控件的Name属性。当然我们也可以显示申明要执行IPostBackEventHandler 接口。 重写OnLoad 方法如下:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Page.RegisterRequiresRaiseEvent(this);
}
重新启动调试, Ok, 没问题了。
总结: 以上的示例, 是我随便写的最简单的情况, 实际开发中情况可能复杂的多, 我建议有兴趣的朋友在理解了控件生命周期的情况下去看看 .NET 自带的控件库的源代码。
相信对大家一定会大有助益的。 下一篇让我们一起来学习 组合控件的开发。