ASP.NET服务器控件开发(4)--复合控件
复合控件
概念:
所谓复合控件:简单的理解就是将多个基本的控件组合成一个控件,从而实现自己想要的效果。微软为ASP.NET2.0中推出的登录控件等就是一个复合控件。从功能的实现上,复合式控件有点像用户控件,只是一个是.ascx文件,一个是.dll文件。
呈现简单的复合控件:
要想呈现一个复合控件,需要了解以下几个方面:
-->实现INamingContainer接口。
任何实现该接口的控件都创建一个新的命名空间,在这个新的命名空间中,所有子控件 ID 属性在整个应用程序内保证是唯一的。
-->Control.CreateChildControls 方法。
由 ASP.NET 页面框架调用,以通知使用基于合成的实现的服务器控件创建它们包含的任何子控件,以便为回发或呈现做准备。 当开发复合服务器控件或模板服务器控件时,必须重写此方法。重写 CreateChildControls 方法的控件应实现 INamingContainer 接口以避免命名冲突。
-->Control.ChildControlsCreated 属性。
获取一个值,该值指示是否已创建服务器控件的子控件。
-->Control.EnsureChildControls 方法。
确定服务器控件是否包含子控件。如果不包含,则创建子控件。
下面就通过实例来呈现个简单的复合登陆控件:创建ASP.NET服务器控件工程。complexControl。
先来看代码:
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:LoginControl runat=server ButtonText='登录' NameLabel='用户名:' PasswordLabel='用户密码:'></{0}:LoginControl>")]
public class LoginControl : WebControl, INamingContainer, IPostBackEventHandler
{
private Button _button;
private TextBox _nameTextBox;
private Label _nameLabel;
private TextBox _passwordTextBox;
private Label _passwordLabel;
private RequiredFieldValidator _nameValidator;
private RequiredFieldValidator _passwordValidator;
[Bindable(true),Category("Appearance"),DefaultValue(""),Description("按钮文本")]
public string ButtonText
{
get
{
EnsureChildControls();//确定服务器控件是否包含子控件
return _button.Text;
}
set
{
EnsureChildControls();
_button.Text = value;
}
}
[Bindable(true),Category("Default"),DefaultValue(""),Description("姓名")]
public string Name
{
get
{
EnsureChildControls();
return _nameTextBox.Text;
}
set
{
EnsureChildControls();
_nameTextBox.Text = value;
}
}
[Bindable(true),Category("Appearance"),DefaultValue(""),Description("必须输入姓名")]
public string NameErrorMessage
{
get
{
EnsureChildControls();
return _nameValidator.ErrorMessage;
}
set
{
EnsureChildControls();
_nameValidator.ErrorMessage = value;
_nameValidator.ToolTip = value;
}
}
[Bindable(true),Category("Apperance"),DefaultValue(""),Description("姓名标签")]
public string NameLabel
{
get
{
EnsureChildControls();
return _nameLabel.Text;
}
set
{
EnsureChildControls();
_nameLabel.Text = value;
}
}
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string Password
{
get
{
EnsureChildControls();
return _passwordTextBox.Text;
}
}
[Bindable(true),Category("Appearance"),DefaultValue(""),Description("必须输入密码")]
public string PasswordErrorMessage
{
get
{
EnsureChildControls();
return _passwordValidator.ErrorMessage;
}
set
{
EnsureChildControls();
_passwordValidator.ErrorMessage = value;
_passwordValidator.ToolTip = value;
}
}
[Bindable(true),Category("Appearance"),DefaultValue(""),Description("密码标签")]
public string PasswordLabel
{
get
{
EnsureChildControls();
return _passwordLabel.Text;
}
set
{
EnsureChildControls();
_passwordLabel.Text = value;
}
}
protected override void CreateChildControls()
{
Controls.Clear();
_nameLabel = new Label();
_nameTextBox = new TextBox();
_nameTextBox.ID = "nameTextBox";
_nameValidator = new RequiredFieldValidator();
_nameValidator.ID = "validator1";
_nameValidator.ControlToValidate = _nameTextBox.ID;
_nameValidator.Text = "*";
_nameValidator.Display = ValidatorDisplay.Static;
_passwordLabel = new Label();
_passwordTextBox = new TextBox();
_passwordTextBox.TextMode = TextBoxMode.Password;
_passwordTextBox.ID = "passwordTextBox";
_passwordValidator = new RequiredFieldValidator();
_passwordValidator.ID = "validator2";
_passwordValidator.ControlToValidate = _passwordTextBox.ID;
_passwordValidator.Text = "*";
_passwordValidator.Display = ValidatorDisplay.Static;
_button = new Button();
_button.ID = "button1";
//_button.Click += new EventHandler(_button_Click);
_button.CommandName = "ClickLogin";
this.Controls.Add(_nameLabel);
this.Controls.Add(_nameTextBox);
this.Controls.Add(_nameValidator);
this.Controls.Add(_passwordLabel);
this.Controls.Add(_passwordTextBox);
this.Controls.Add(_passwordValidator);
this.Controls.Add(_button);
}
protected override void Render(HtmlTextWriter writer)
{
AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding,
"1", false);
writer.RenderBeginTag(HtmlTextWriterTag.Table);
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_nameLabel.RenderControl(writer);
writer.RenderEndTag(); // Td
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_nameTextBox.RenderControl(writer);
writer.RenderEndTag(); // Td
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_nameValidator.RenderControl(writer);
writer.RenderEndTag(); // Td
writer.RenderEndTag(); // Tr
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_passwordLabel.RenderControl(writer);
writer.RenderEndTag(); // Td
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_passwordTextBox.RenderControl(writer);
writer.RenderEndTag(); // Td
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_passwordValidator.RenderControl(writer);
writer.RenderEndTag(); // Td
writer.RenderEndTag(); // Tr
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.AddAttribute(HtmlTextWriterAttribute.Colspan, "2");
writer.AddAttribute(HtmlTextWriterAttribute.Align, "right");
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_button.RenderControl(writer);
//writer.AddAttribute(HtmlTextWriterAttribute, Page.GetPostBackEventReference(_button));
writer.RenderEndTag(); // Td
writer.RenderBeginTag(HtmlTextWriterTag.Td);
writer.Write(" ");
writer.RenderEndTag(); // Td
writer.RenderEndTag(); // Tr
writer.RenderEndTag(); // Table
}
}
}
首先我们实例化了几个现有控件的对象。然后声明了一大堆的属性,要注意的:和平时定义属性不同,我们在每一个属性中都添加了EnsureChildControls ()方法。其他的没有任何变化,和一般的属性声明一样。
接下来我们从写了重要的CreateChildControls()。将前面声明好的实例化控件对象添加到controlcollection中。融合成一个控件。
最后重写控件显示的Render()方法。生成登录窗体的样式。效果如下:
这样,我们基本上就完成了复合控件的基本显示功能。
复合控件的事件处理:
由于复合控件中包含子控件,这就使得复合控件的事件处理变得复杂起来。由于不允许开发人员直接访问子控件,如果子控件的事件不能作为顶级事件引发,那么将无法实现子控件的事件处理。
我们可以以两种形式来完成事件的处理:一是直接将事件封装到控件中,显然灵活性很差。二就是自定义事件,用户来完成事件的代码。
第一种情况比较简单:就是在创建我们得控件时,将要实现的效果直接封装在dll中。这里就不做说明了。
但是往往控件触发时,我们想做自己的事情,这就是第二种情况的事件处理。这就需要把事件交给主控件,由主控件统一暴露事件,这样开发人员在使用控件时仅需要为主控件注册事件即可,剩下的由主控件负责引发子控件的事件或执行子控件的某些功能,这里就涉及主控件与其子控件的事件衔接问题,复合控件的这种事件处理,主要是实现子控件事件上传的过程。一般分为:包含法和冒泡法两种处理方式。
-->包含法:
基本思想是:通过在子控件的事件处理程序中调用复合控件的顶层事件处理程序,以完成子控件的事件上传。 在CreateChildControls方法中,为子控件添加事件处理程序。
接着上面登陆控件的例子,来实现下登录按钮的事件。
首先在CreateChildControls()中,为_button添加单击事件。(其他代码略)
_button.ID = "button1";
_button.Click += new EventHandler(_button_Click);
然后创建主控件对外的处理函数:
{
OnClickLogin(EventArgs.Empty);
}
private static readonly object EventClickLogin = new object();
public event EventHandler ClickLogin
{
add
{
Events.AddHandler(EventClickLogin, value);
}
remove
{
Events.RemoveHandler(EventClickLogin, value);
}
}
protected virtual void OnClickLogin(EventArgs e)
{
EventHandler clickLoginHandler = (EventHandler)Events[EventClickLogin];
if (clickLoginHandler != null)
{
clickLoginHandler(this, e);
}
}
public void RaisePostBackEvent(string eventArgument)//处理回发事件
{
OnClickLogin(new EventArgs());
}
事件的详细处理请参看上一篇。这里要说明的是:在按钮的单击事件处理函数中,将我们在主控件中声明的事件传入进去:
{
OnClickLogin(EventArgs.Empty);
}
这样,我们就实现了第一种方法。测试一下:
{
Label1.Text = "sssssssssssssssqwwssss";
}
单击按钮,将触发上面的事件。
-->冒泡法:
基本思想:使用ASP.NET 2.0框架提供的事件上传机制。这种机制允许子控件将事件沿其包容层次结构向上传播到合适的位置引发,并且允许将事件处理程序附加到原始控件以及公开冒泡的事件的控件上。
冒泡法的实现,使用Control基类中专门用于事件上传的两个方法:OnBubbleEvent和RaiseBubbleEvent。OnBubbleEvent方法用于确定子控件的事件是否沿复合控件层次结构向上传递。在该方法中,参数source表示事件源,参数args表示包含事件数据的EventArgs对象。如果子控件的事件向上传递,则为true;否则为false。默认值为false。RaiseBubbleEvent方法用于将所有事件源及其信息分配给控件的父级,并且不能被重写。尽管无法重写此方法,但创作的控件可以通过重写 OnBubbleEvent 方法处理或引发冒泡事件。
还是通过例子说明一下:
首先在CreateChildControls()中声明commandname属性。
然后定义事件:
private static readonly object EventClickLogin = new object();
public event EventHandler ClickLogin
{
add
{
Events.AddHandler(EventClickLogin, value);
}
remove
{
Events.RemoveHandler(EventClickLogin, value);
}
}
protected virtual void OnClickLogin(EventArgs e)
{
EventHandler clickLoginHandler = (EventHandler)Events[EventClickLogin];
if (clickLoginHandler != null)
{
clickLoginHandler(this, e);
}
}
protected override bool OnBubbleEvent(object source, EventArgs e)
{
bool handled = false;
if (e is CommandEventArgs)
{
CommandEventArgs ce = (CommandEventArgs)e;
if (ce.CommandName == "ClickLogin")
{
OnClickLogin(EventArgs.Empty);
handled = true;
}
}
return handled;
}
public void RaisePostBackEvent(string eventArgument)//处理回发事件
{
OnClickLogin(new EventArgs());
}
里主要要注意的是:OnBubbleEvent()的使用。通过CommandName的值,来相应的找到处理事件的控件。
测试一下:
{
Label1.Text = "sssssssssssssssqwwssss";
}
单击按钮,将触发上面的事件。
小结:这样,复合控件的基本使用就介绍完了,不是很难。只要记住特定的一些东西,就可以很容易的创造出复合控件。值得大家注意的是复合控件中事件的两种处理方法。希望对新手有帮助。