“做.NET不值钱,没有技术含量,拖拖控件就行了。”———— 这类言语我最讨厌了,一点技术含量都没有,不懂.NET就瞎嚷嚷。不管做什么,做好都不容易,你没做过怎么知道没有技术含量呢? 我们既享受控件带来的快捷,也知其原理,甚至可以自己开发控件,你还能说我们没技术含量?!
控件是什么? 是.NET Framework对Html里的元素的封装。 他把一个<input type="button"/>变成了 Button类,把<span></span>变成了Label类,把<input type="select">变成了DropDownList。这就是控件,就是这些类。
上节我们提到了Page类,Page的生命周期里有些事件是和处理控件类有关的。
PreInit
使用该事件来执行下列操作:
检查 IsPostBack 属性来确定是不是第一次处理该页。
创建或重新创建动态控件。
动态设置主控页。
动态设置 Theme 属性。
读取或设置配置文件属性值。
Render
这不是事件;在处理的这个阶段,Page 对象会在每个控件上调用此方法。所有 ASP.NET Web 服务器控件都有一个用于写出发送给浏览器的控件标记的 Render 方法。
如果创建自定义控件,通常要重写此方法以输出控件的标记。不过,如果自定义控件只合并标准的 ASP.NET Web 服务器控件,不合并自定义标记,则不需要重写 Render 方法。
一个是初始化,一个是显示,其他的还有事件之类的,我们暂且不提。我们可以看出,初始化其实就是实例化desinger.cs里用到的控件。而最终这些控件都需要转换成HTML标签,也就是被Page调用其Render方法。Page自身也是控件所,以Page的Render就是负责生成HTML的。
控件类都在System.Web.UI.WebControls下,我们可以打开MSDN查看一些常用控件的类和其相关的事件方法。如果有用Refletor的话,可以打开System.Web.dll查看更加详细的源码。你会发现有意思的继承关系,理解MS的设计理念,也更方便我们对控件有个更加清晰的认识。
Button类。
当我们在FORM里放置一个Button,该按钮默认为Submit按钮,如果设为UseSubmitBehavior = false,就可以看到客户端会生成该按钮需要的JS代码。
<
div
class
="aspNetHidden"
>
< input type ="hidden" name ="__EVENTTARGET" id ="__EVENTTARGET" value ="" />
< input type ="hidden" name ="__EVENTARGUMENT" id ="__EVENTARGUMENT" value ="" />
< input type ="hidden" name ="__VIEWSTATE" id ="__VIEWSTATE" value ="/wEPDwUKMTcxODU4OTc0MWRk6Htqf5QF2exQx/BOtdDx1djNfbW7y3bPbmRizhypOr4=" />
</ div >
< script type ="text/javascript" >
// <![CDATA[
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 >
< input type ="submit" name ="Button1" value ="Button" id ="Button1" />
< input type ="button" name ="Button2" value ="Button" onclick ="javascript:__doPostBack('Button2',')" id ="Button2" />
< input type ="hidden" name ="__EVENTTARGET" id ="__EVENTTARGET" value ="" />
< input type ="hidden" name ="__EVENTARGUMENT" id ="__EVENTARGUMENT" value ="" />
< input type ="hidden" name ="__VIEWSTATE" id ="__VIEWSTATE" value ="/wEPDwUKMTcxODU4OTc0MWRk6Htqf5QF2exQx/BOtdDx1djNfbW7y3bPbmRizhypOr4=" />
</ div >
< script type ="text/javascript" >
// <![CDATA[
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 >
< input type ="submit" name ="Button1" value ="Button" id ="Button1" />
< input type ="button" name ="Button2" value ="Button" onclick ="javascript:__doPostBack('Button2',')" id ="Button2" />
__doPostBack就是出发form的提交,而Button2调用的时候传值了自己的名字,然后__EVENTTARGET的值被设为了Button2,这样在提交的时候,Post的数据里就会告诉服务端是Button2触发的!再看Button1却没有调用__doPostBack,因为他是submit类型,在提交的时候浏览器会把他的名字默认提交过去,同时其他的按钮不会被提交。
所以,既然服务端知道了是哪个按钮被点过,自然就会调用该按钮实例的OnClick方法。看看Page类里有一个方法。
if
(
this
.IsPostBack)
{
this .RaisePostBackEvent( this ._requestValueCollection);
}
private void RaisePostBackEvent(NameValueCollection postData)
{
if ( this ._registeredControlThatRequireRaiseEvent != null )
{
this .RaisePostBackEvent( this ._registeredControlThatRequireRaiseEvent, null );
}
else
{
string str = postData[ " __EVENTTARGET " ];
bool flag = ! string .IsNullOrEmpty(str);
if (flag || ( this .AutoPostBackControl != null ))
{
Control control = null ;
if (flag)
{
control = this .FindControl(str);
}
if ((control != null ) && (control.PostBackEventHandler != null ))
{
string eventArgument = postData[ " __EVENTARGUMENT " ];
this .RaisePostBackEvent(control.PostBackEventHandler, eventArgument);
}
}
else
{
this .Validate();
}
}
}
{
this .RaisePostBackEvent( this ._requestValueCollection);
}
private void RaisePostBackEvent(NameValueCollection postData)
{
if ( this ._registeredControlThatRequireRaiseEvent != null )
{
this .RaisePostBackEvent( this ._registeredControlThatRequireRaiseEvent, null );
}
else
{
string str = postData[ " __EVENTTARGET " ];
bool flag = ! string .IsNullOrEmpty(str);
if (flag || ( this .AutoPostBackControl != null ))
{
Control control = null ;
if (flag)
{
control = this .FindControl(str);
}
if ((control != null ) && (control.PostBackEventHandler != null ))
{
string eventArgument = postData[ " __EVENTARGUMENT " ];
this .RaisePostBackEvent(control.PostBackEventHandler, eventArgument);
}
}
else
{
this .Validate();
}
}
}
啊哈!明白没有?在IsPostBack(也就是提交)后,调用了触发所有控件事件的方法。postData["__EVENTTARGET"]明显就是我们刚才的点的按钮名称,然后通过FindControl得到该对象,最后调用RaisePostBackEvent方法出发该对象的事件。再往下看具体流程:
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)
{
sourceControl.RaisePostBackEvent(eventArgument);
}
protected virtual void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)
{
sourceControl.RaisePostBackEvent(eventArgument);
}
也就是说,Button类必须实现IPostBackEventHandler接口(因为Page不知道控件到底是什么,所以要使用接口,这就是接口的用处)。
看看具体实现:
protected
virtual
void
RaisePostBackEvent(
string
eventArgument)
{
base .ValidateEvent( this .UniqueID, eventArgument);
if ( this .CausesValidation)
{
this .Page.Validate( this .ValidationGroup);
}
this .OnClick(EventArgs.Empty);
this .OnCommand( new CommandEventArgs( this .CommandName, this .CommandArgument));
}
{
base .ValidateEvent( this .UniqueID, eventArgument);
if ( this .CausesValidation)
{
this .Page.Validate( this .ValidationGroup);
}
this .OnClick(EventArgs.Empty);
this .OnCommand( new CommandEventArgs( this .CommandName, this .CommandArgument));
}
首先是验证,然后是执行OnClick,然后执行OnCommand。呵呵,一个OnClick我们终于走完了~ 感觉如何? 头涨了没?
看下Button类,确实继承IPostBackEventHandler接口。
[Designer(
"
System.Web.UI.Design.WebControls.ButtonDesigner, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
"
), SupportsEventValidation, ToolboxData(
"
<{0}:Button runat=\
"
server\
"
Text=\
"
Button\
"
></{0}:Button>
"
), DefaultEvent(
"
Click
"
), DefaultProperty(
"
Text
"
), DataBindingHandler(
"
System.Web.UI.Design.TextDataBindingHandler, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
"
)]
public class Button : WebControl, IButtonControl, IPostBackEventHandler
{
....
[WebCategory( " Action " ), WebSysDescription( " Button_OnClick " )]
public event EventHandler Click;
protected virtual void OnClick(EventArgs e)
[WebCategory( " Action " ), WebSysDescription( " Button_OnCommand " )]
public event CommandEventHandler Command;
[WebSysDescription( " Button_Text " ), WebCategory( " Appearance " ), Bindable( true ), Localizable( true ), DefaultValue( "" )]
public string Text { get ; set ; }
....
}
public class Button : WebControl, IButtonControl, IPostBackEventHandler
{
....
[WebCategory( " Action " ), WebSysDescription( " Button_OnClick " )]
public event EventHandler Click;
protected virtual void OnClick(EventArgs e)
[WebCategory( " Action " ), WebSysDescription( " Button_OnCommand " )]
public event CommandEventHandler Command;
[WebSysDescription( " Button_Text " ), WebCategory( " Appearance " ), Bindable( true ), Localizable( true ), DefaultValue( "" )]
public string Text { get ; set ; }
....
}
并且看到熟悉的Click,Command,Text,而ID,Width,Height则在他的父类中。
看看OnClick是干嘛的?
protected
virtual
void
OnClick(EventArgs e)
{
EventHandler handler = (EventHandler) base .Events[EventClick];
if (handler != null )
{
handler( this , e);
}
}
{
EventHandler handler = (EventHandler) base .Events[EventClick];
if (handler != null )
{
handler( this , e);
}
}
OnClick的方法是被点击后执行的方法,具体是从Events里找到EventClick类型的委托,其实就是判断Click事件是否被订阅,如果有该类型委托,则表明已经被订阅,便执行该委托实例(也就是我们定义的Click具体方法)。
再来看下是如何订阅的:
[WebCategory(
"
Action
"
), WebSysDescription(
"
Button_OnClick
"
)]
public event EventHandler Click
{
add
{
base .Events.AddHandler(EventClick, value);
}
remove
{
base .Events.RemoveHandler(EventClick, value);
}
}
public event EventHandler Click
{
add
{
base .Events.AddHandler(EventClick, value);
}
remove
{
base .Events.RemoveHandler(EventClick, value);
}
}
Click事件具有add和remove两个类似属性的get和set的方法,这当然是C#的特定语法,实际编译后的IL里是两个小方法。
Add是订阅,remove是取消订阅。AddHandler就是把具体的方法和委托类型添加到Events里。
public
void
AddHandler(
object
key, Delegate value)
{
ListEntry entry = this .Find(key);
if (entry != null )
{
entry.handler = Delegate.Combine(entry.handler, value);
}
else
{
this .head = new ListEntry(key, value, this .head);
}
}
{
ListEntry entry = this .Find(key);
if (entry != null )
{
entry.handler = Delegate.Combine(entry.handler, value);
}
else
{
this .head = new ListEntry(key, value, this .head);
}
}
一气呵成,我们知道了Click的具体调用实现,且明白的事件的订阅。后面再说控件的其他属性,休息休息。后面会介绍Render和DataBind :)