P70
通常,复合控件组合了两个或者多个存在的服务器控件,其子控件可以是ASP.NET框架内置的服务器控件,也可以是自定义的服务器控件。
下面列出实现复合控件的必要步骤。
·控件类必须派生自System.Web.UI.WebControls.WebControl或者System.Web.UI.WebControls基类,并且实现System.Web.UI.INamingContainer接口。
·必须重写CreateChildControls方法来对子控件进行初始化、实例化,并将其添加到控件树中。
在这一章里,讲的是复合控件的四个东西,属性、控件呈现、事件和样式,就一个例子,做一个放在一行两列的table里的TextBox和Button组合成的服务器控件,因为事件有两种方法实现,包含法和冒泡法,所以分了两个程序,我一共按书上的做了三个控件
复合控件的属性和前面学的都一样,用的是getset访问器,就是,这里使用了一个EnsureChildControls()方法
P70
我做的第一个例子,CompositeControl.cs,在把做好的服务器控件拖入设计面板时,会出现
的错,根据上面的解释,我觉得应该是控件在一开始时,因为没有调用过CreateChildControls方法,所以子控件没有被加入控件树中,所以引用Text属性的_button就是null值,当我们使用set访问器设置Text属性时,EnsureChildControls方法会检测到子控件还没有生成,会自动调用CreateChildControls方法生成子控件,所以在设置了Text属性后,控件就可以在设计面板里正确呈现了,不会再出现null值的错误
所以我加入了构造函数,在构造函数里,直接调用ChildControlsCreated方法,就是在一开始把控件拖入设计面板时,就调用ChildControlsCreated方法把子控件加入控件树,加入构造函数后,上面的出错已经解决
复合控件的事件,我的理解就是要把对整个复合控件的事件触发,转到对里面子控件的事件触发,书上讲了两种方法:包含法、冒泡法
包含法对应的例子是也是CompositeControl.cs文件
P74
包含法的核心是,通过在子控件的事件处理程序中调用复合控件的顶层事件处理程序,以完成子控件的事件上传。在执行过程中,当引发子控件事件后,子控件的事件处理程序将自动调用相关顶层事件处理程序。
我是从例子上看着理解的,在程序里,先是加入一个事件委托
然后加入了一个对整个服务器控件的事件
然后,实现这个事件
这三步,为整个复合控件添加了一个ButtonClick事件,这个事件就是顶层事件
在第四章P50 4.2.2捕获回传事件的实现那个例子里(user1/9/archives/2007/3876.html),是通过实现IPostBackEventHandler接口和在RaisePostBackEvent方法中调用OnClick方法来捕捉回传的事件的,当时那个例子,是简单的例子,那个button本身,也是在重写RenderContents方法是写的
这个控件不是服务器控件,所以他回传的事件,是要自己捕捉的,现在做的这个例子里,做的是复合控件,Button本身,是一个服务器控件,他自己是有一个回传的事件的,我想这个包含法就是指利用这个事件,调用上面加入的顶层事件的意思
例子里在重写CreateChildControls方法时,为子控件加入了单击事件
加入的这个单击事件是的定义是这样的
在单击子控件的时候,让这个子服务器控件自己回传事件,然后在调用顶层事件OnButtonClick
下面是CompositeControl.cs的完整代码
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
namespace P072WebControlLibrary
{
public class CompositeControl : WebControl,INamingContainer
{
//声明变量
private Button _button;
private TextBox _textBox;
//定义事件委托对象
private static readonly object EventButtonClick = new object();
//定义属性Text,用于指定按钮上的文字
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Description("获取或设置显示在按钮上的文字")]
public string Text
{
get
{
EnsureChildControls();
return _button.Text;
}
set
{
EnsureChildControls();
_button.Text = value;
}
}
//加入构造函数,在此控件实例化的时候,就创建子控件
//如果没有这个,Text属性如果不设置,在设计面板里会出现:呈现控件时出错:发生了未处理的异常。未将对象引用设置到对象的实例。
public CompositeControl()
{
CreateChildControls();
}
//重写Controls属性
public override ControlCollection Controls
{
get
{
EnsureChildControls();
return base.Controls;
}
}
//重写CreateChildControls方法,将子控件添加到复合控件中
protected override void CreateChildControls()
{
Controls.Clear();
_button = new Button();
_textBox = new TextBox();
_button.ID = "btn";
//为提交按钮添加事件处理程序
_button.Click += new EventHandler(this._button_Click);
//把子控件加入控件树
this.Controls.Add(_button);
this.Controls.Add(_textBox);
}
//重写Render方法,呈现控件中其他的HTML代码
protected override void Render(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Border, "0px");
writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "5px");
writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0px");
writer.RenderBeginTag(HtmlTextWriterTag.Table);
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.RenderBeginTag(HtmlTextWriterTag.Td);
//把服务器控件的内容加入html输出流
_textBox.RenderControl(writer);
writer.RenderEndTag();
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_button.RenderControl(writer);
writer.RenderEndTag();
writer.RenderEndTag();
writer.RenderEndTag();
}
//事件处理
[Description("当点击按钮是触发")]
public event EventHandler ButtonClick
{
add
{
Events.AddHandler(EventButtonClick, value);
}
remove
{
Events.RemoveHandler(EventButtonClick, value);
}
}
protected virtual void OnButtonClick(EventArgs e)
{
EventHandler buttonClickHandler = (EventHandler)Events[EventButtonClick];
if (buttonClickHandler != null)
{
buttonClickHandler(this, e);
}
}
protected void _button_Click(object sender, EventArgs e)
{
OnButtonClick(e);
}
}
}
p78
冒泡法也称“事件冒泡”,其核心是合用ASP.NET框架提供的事件上传机制。这种机制允许子控件将事件沿其包含层次结构向上传播到合适的位置引发,并且允许将事件处理程序附加到原始控件以及公开冒泡的事件的控件上。
冒泡法的实现,使用Control基类中专门用于事件上传的两个方法OnBubbleEvent和RaiseBubbleEvent。
OnBubbleEvent方法用于确定子控件的事件是否沿复合控件层次结构向上传递,如果事件已被处理,则为true,否则为false,默认值为false。RaiseBubbleEvent方法 用于将所有事件源及其信息分配给控件的父级,并且不能被重写。若要处理或引发冒泡的事件,控件必须重写OnBubbleEvent方法。
这个例子的文件是WebCustomControlP79.cs,例子源码在P79,在这个例子里,同样是先为整个复合控件加入ButtonClick事件,使用EventButtonClick做事件委托,用OnButtonClick()方法实现
然后,这个例子在重写CreateChildControls()时,没有为子控件添加单击事件,而是给子控件设置CommandName属性
最后,这个例子重写了OnBubbleEvent()方法,在这个方法里,引发OnButtonClick()方法
下面是WebCustomControlP79.cs文件的完整源码
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
namespace P072WebControlLibrary
{
public class WebCustomControlP79 : WebControl, INamingContainer
{
//声明变量
private Button _button;
private TextBox _textBox;
//定义事件委托对象
private static readonly object EventButtonClick = new object();
//定义属性Text,用于指定按钮上的文字
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Description("获取或设置显示在按钮上的文字")]
public string Text
{
get
{
EnsureChildControls();
return _button.Text;
}
set
{
EnsureChildControls();
_button.Text = value;
}
}
//重写Controls属性
public override ControlCollection Controls
{
get
{
EnsureChildControls();
return base.Controls;
}
}
//重写CreateChildControls方法,将子控件添加到复合控件中
protected override void CreateChildControls()
{
Controls.Clear();
_button = new Button();
_textBox = new TextBox();
_button.ID = "btn";
//给子控件设置CommandName属性
_button.CommandName = "ButtonClick";
this.Controls.Add(_button);
this.Controls.Add(_textBox);
}
//重写Render方法,呈现控件中其他的HTML代码
protected override void Render(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Border, "0px");
writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "5px");
writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0px");
writer.RenderBeginTag(HtmlTextWriterTag.Table);
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.RenderBeginTag(HtmlTextWriterTag.Td);
//把服务器控件的内容加入html输出流
_textBox.RenderControl(writer);
writer.RenderEndTag();
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_button.RenderControl(writer);
writer.RenderEndTag();
writer.RenderEndTag();
writer.RenderEndTag();
}
//事件处理
[Description("当点击按钮是触发")]
public event EventHandler ButtonClick
{
add
{
Events.AddHandler(EventButtonClick, value);
}
remove
{
Events.RemoveHandler(EventButtonClick, value);
}
}
protected virtual void OnButtonClick(EventArgs e)
{
EventHandler buttonClickHandler = (EventHandler)Events[EventButtonClick];
if (buttonClickHandler != null)
{
buttonClickHandler(this, e);
}
}
protected override bool OnBubbleEvent(object sender, EventArgs e)
{
bool handled = false;
if (e is CommandEventArgs)
{
CommandEventArgs ce = (CommandEventArgs)e;
if (ce.CommandName == "ButtonClick")
{
OnButtonClick(EventArgs.Empty);
handled = true;
}
}
return handled;
}
}
}
这一章讲的最后一个问题就是6.4 复合控件的样式,在这一节里,讲了怎么给子控件添加样式,从例子上看好像很简单,就是添加变量,设置样式属性,在重写Render方法时,加入样式的处理,最后就是重写三个方法:LoadViewState、SaveViewState、TrackViewState,在重写这三个方法的时候,要为基类样式和每一个子控件的样式做设置
下面是WebCustomControlP82.cs的完整源码
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
namespace P072WebControlLibrary
{
[DefaultEvent("Button")]
[DefaultProperty("Text")]
public class WebCustomControlP82 : WebControl, INamingContainer
{
//声明变量
private Button _button;
private TextBox _textBox;
//定义事件委托对象
private static readonly object EventButtonClick = new object();
//定义两个样式
private Style _buttonStyle;
private Style _textBoxStyle;
//定义属性Text,用于指定按钮上的文字
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Description("获取或设置显示在按钮上的文字")]
public string Text
{
get
{
EnsureChildControls();
return _button.Text;
}
set
{
EnsureChildControls();
_button.Text = value;
}
}
//定义ButtonStyle属性
[
Category("Style"),
Description("设置Button的样式属性"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public virtual Style ButtonStyle
{
get
{
if (_buttonStyle == null)
{
_buttonStyle = new Style();
if (IsTrackingViewState)
{
((IStateManager)_buttonStyle).TrackViewState();
}
}
return _buttonStyle;
}
}
//定义TextStyle属性
[
Category("Style"),
Description("设置TextBox的样式属性"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public virtual Style TextBoxStyle
{
get
{
if (_textBoxStyle == null)
{
_textBoxStyle = new Style();
if (IsTrackingViewState)
{
((IStateManager)_textBoxStyle).TrackViewState();
}
}
return _textBoxStyle;
}
}
//重写Controls属性
public override ControlCollection Controls
{
get
{
EnsureChildControls();
return base.Controls;
}
}
//重写CreateChildControls方法,将子控件添加到复合控件中
protected override void CreateChildControls()
{
Controls.Clear();
_button = new Button();
_textBox = new TextBox();
_button.ID = "btn";
//给子控件设置CommandName属性
_button.CommandName = "ButtonClick";
this.Controls.Add(_button);
this.Controls.Add(_textBox);
}
//重写Render方法,呈现控件中其他的HTML代码
protected override void Render(HtmlTextWriter writer)
{
//把添加的样式加到html输出流
AddAttributesToRender(writer);
if (_textBoxStyle != null)
{
_textBox.ApplyStyle(TextBoxStyle);
}
if (_buttonStyle != null)
{
_button.ApplyStyle(ButtonStyle);
}
//输出控件
writer.AddAttribute(HtmlTextWriterAttribute.Border, "0px");
writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "5px");
writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0px");
writer.RenderBeginTag(HtmlTextWriterTag.Table);
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.RenderBeginTag(HtmlTextWriterTag.Td);
//把服务器控件的内容加入html输出流
_textBox.RenderControl(writer);
writer.RenderEndTag();
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_button.RenderControl(writer);
writer.RenderEndTag();
writer.RenderEndTag();
writer.RenderEndTag();
}
//事件处理
[Description("当点击按钮是触发")]
public event EventHandler ButtonClick
{
add
{
Events.AddHandler(EventButtonClick, value);
}
remove
{
Events.RemoveHandler(EventButtonClick, value);
}
}
//定义OnButtonClick
protected virtual void OnButtonClick(EventArgs e)
{
EventHandler buttonClickHandler = (EventHandler)Events[EventButtonClick];
if (buttonClickHandler != null)
{
buttonClickHandler(this, e);
}
}
//重写OnBubbleEvent,调用引发冒泡的事件的OnEventName方法
protected override bool OnBubbleEvent(object sender, EventArgs e)
{
bool handled = false;
if (e is CommandEventArgs)
{
CommandEventArgs ce = (CommandEventArgs)e;
if (ce.CommandName == "ButtonClick")
{
OnButtonClick(EventArgs.Empty);
handled = true;
}
}
return handled;
}
//复杂样式属性的状态管理,重写三个相关方法
protected override void LoadViewState(object savedState)
{
if (savedState == null)
{
base.LoadViewState(null);
return;
}
if (savedState != null)
{
object[] myState = (object[])savedState;
if (myState.Length != 3)
{
//只有三个样式:基类的样式、_textBoxStyle、_buttonStyle
throw new ArgumentException("无效的ViewState");
}
base.LoadViewState(myState[0]);
if (myState[1] != null)
{
((IStateManager)TextBoxStyle).LoadViewState(myState[1]);
}
if (myState[2] != null)
{
((IStateManager)ButtonStyle).LoadViewState(myState[2]);
}
}
}
protected override object SaveViewState()
{
object[] myState = new object[3];
myState[0] = base.SaveViewState();
myState[1] = (_textBoxStyle != null) ? ((IStateManager)_textBoxStyle).SaveViewState() : null;
myState[2]=(_buttonStyle!=null)?((IStateManager)_buttonStyle).SaveViewState():null;
for(int i=0;i<3;i++)
{
if(myState[i]!=null)
{
return myState;
}
}
return null;
}
protected override void TrackViewState()
{
base.TrackViewState();
if (_buttonStyle != null)
{
((IStateManager)_buttonStyle).TrackViewState();
}
if (_textBoxStyle != null)
{
((IStateManager)_textBoxStyle).TrackViewState();
}
}
}
}
上面三个自定义服务器控件的调用测试文件是这个P72_CompositeControl.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="P72_CompositeControl.aspx.cs" Inherits="P72_CompositeControl" %>
<%@ Register Assembly="P072WebControlLibrary" Namespace="P072WebControlLibrary" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>无标题页</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:CompositeControl ID="CompositeControl1" runat="server" OnButtonClick="CompositeControl1_ButtonClick" Text="提交" />
<br />
<asp:Label ID="Label1" runat="server"></asp:Label><br />
<br />
下面这个是P79的例子,冒泡法上传事件<br />
<cc1:WebCustomControlP79 ID="WebCustomControlP79_1" runat="server" OnButtonClick="WebCustomControlP79_1_ButtonClick"
Text="提交" />
<br />
<br />
下面是P82的例子,复杂样式<br />
<cc1:WebCustomControlP82 ID="WebCustomControlP82_1" runat="server" ButtonStyle-Height="24px"
ButtonStyle-Width="84px" Text="提交" TextBoxStyle-BorderWidth="1px" TextBoxStyle-Height="24px"
TextBoxStyle-Width="198px">
<ButtonStyle Height="24px" Width="84px" />
<TextBoxStyle BorderWidth="1px" Height="24px" Width="198px" />
</cc1:WebCustomControlP82>
</div>
</form>
</body>
</html>
P72_CompositeControl.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class P72_CompositeControl : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void CompositeControl1_ButtonClick(object sender, EventArgs e)
{
Label1.Text = "包含法上传事件";
}
protected void WebCustomControlP79_1_ButtonClick(object sender, EventArgs e)
{
Label1.Text = "冒泡法上传事件";
}
}