5.4 用户控件编程

到现在为止,相信你对用户控件也有了一定的了解,并可以根据需要创建自己的用户控件。本节将在此基础上继续深入讨论用户控件的各种编程技巧,如处理用户控件自己的事件,为用户控件添加属性、方法和事件处理程序等。

为了能够更好地理解用户控件的开发,本节将通过创建一个“网站友情链接”用户控件来贯穿用户控件开发的各项编程技巧。事实上,网站友情链接也是常见的用户控件之一。

5.4.1 处理用户控件事件

“网站友情链接”的主要功能就是管理网站的各种友情链接地址,使这些友情链接能够显示在网站需要显示的地方,一般是显示在网页底部。为了实现友情链接的显示,首先需要在项目里面创建一个用户控件HyperLinkControl.ascx,并在该控件里添加一个HyperLink控件来显示链接,HyperLink控件放在Panel控件里,如代码清单5-7所示。

代码清单5-7 HyperLinkControl.ascx


<%@Control Language="C#"AutoEventWireup="true"

CodeBehind="HyperLinkControl.ascx.cs"

Inherits="_5_3.HyperLinkControl"%>

<asp:Panel

ID="Panel1"

runat="server"

EnableTheming="true"

Width="400px"

GroupingText="本站友情链接"

Font-Size="10pt">

<asp:HyperLink

ID="MyHyperLink"

runat="server"

onload="MyHyperLink_Load">

</asp:HyperLink>

</asp:Panel>


为了能够更好地演示用户控件的事件处理功能,在代码清单5-7中,特意为HyperLink控件添加了一个OnLoad事件MyHyperLink_Load。该事件处理程序实现对HyperLink控件相关属性的设置,在Web页面加载用户控件的时候,将触发它来为HyperLink控件属性赋值,代码如下所示:


namespace_5_3

{

public partial class HyperLinkControl:

System.Web.UI.UserControl

{

protected void Page_Load(object sender, EventArgs e)

{

}

protected void MyHyperLink_Load(object sender, EventArgs e)

{

MyHyperLink.Text="本书官方网站";

MyHyperLink.NavigateUrl=

"http://www.comesns.com/aspnet";

}

}

}


创建好HyperLinkControl.ascx用户控件之后,可以在TestHyperLink Control.aspx页面里引用该控件来查看运行结果,如图5-4所示。

figure_0160_0117

图 5-4 TestHyperLinkControl.aspx运行结果

在上面的示例代码中,Hyp erL ink Control.ascx控件和TestHyperLink Control.aspx页面同样都有一个Page_Load事件。这两个文件的Page_Load事件是完全独立的,不具有任何关联性。因此,可以在用户控件Page_Load事件处理程序里面添加相关的初始代码。例如,可以删除代码清单5-7中HyperLink控件的OnLoad事件,并将相关的HyperLink控件的属性赋值语句写到该用户控件的Page_Load事件处理程序中,同样会得到图5-4所示的结果。如下面的代码所示:


public partial class HyperLinkControl:System.Web.UI.UserControl

{

protected void Page_Load(object sender, EventArgs e)

{

if(!Page.IsPostBack)

{

MyHyperLink.Text="本书官方网站";

MyHyperLink.NavigateUrl=

"http://www.comesns.com/aspnet";

}

}

}


注意  在事件执行中,首先执行页面的Page_Load事件,然后执行用户控件的Page_Load事件。所以一般是不在用户控件的Page.Load事件里执行用户控件初始化工作,因为它可能会覆盖客户端指定的设置。

5.4.2 给用户控件添加属性

在代码清单5-7中,该用户控件的友情链接信息是在HyperLink控件的OnLoad事件处理程序里面用代码初始设置的,你不能够在页面上进行设置。因此,这样的用户控件的复用价值几乎没有,也违背了用户控件设计的意见。为了能够让它更具灵活性和复用性,下面将通过为用户控件添加属性的方式来让你可以在页面上自由地设置该控件的友情链接信息。见代码清单5-8。

代码清单5-8 HyperLinkControl.ascx.cs


namespace_5_4

{

public partial class HyperLinkControl:

System.Web.UI.UserControl

{

protected void Page_Load(object sender, EventArgs e)

{

if(!Page.IsPostBack)

{

MyHyperLink.Text=Text;

MyHyperLink.NavigateUrl=Url;

}

}

private string text;

private string url;

public string Text

{

get

{

return text;

}

set

{

text=value;

}

}

public string Url

{

get

{

return url;

}

set

{

url=value;

}

}

}

}


在代码清单5-8中,为HyperLinkControl.ascx控件添加了两个属性。其中,Url属性用于设置链接的地址信息,Text属性用于设置链接的文本信息,并在Page_Load事件处理程序里面通过语句“MyHyperLink.Text=Text”和“MyHyperLink.NavigateUrl=Url”来将这两个属性值赋给HyperLink控件的Text和NavigateUrl属性。

对于页面引用,可以有两种方式进行选择:

第一,在页面的用户控件引用标签里进行设置相关属性值,这样在用户控件第一次被初始化时会对其进行配置。如下所示:


<MyHyperLink:HyperLinkControl

ID="HyperLinkControl1"Text="本书官方网站"

Url="http://www.comesns.com/aspnet"

runat="server"/>


第二,也可以在代码里面通过用户控件的ID进行动态设置。如下所示:


HyperLinkControl1.Text="本书官方网站";

HyperLinkControl1.Url="http://www.comesns.com/aspnet";

注意 如果使用的是简单属性类型,如int、DateTime、float等,在宿主页面声明控件时可以把它们设置为字符串值,ASP.NET会通过类型转换器自动把这些字符串转换为控件类里面定义的属性类型。

5.4.3 使用自定义对象

代码清单5-8中的用户控件虽然能够在页面上通过属性设置的方式设置友情链接信息,但它还是有一些缺陷,如不能够同时设置多个友情链接信息、不能够设置图片链接信息等。要想修改该用户控件的这些功能缺陷,可以使用自定义对象的方式来扩展它。

首先,需要创建一个自定义类HyperLinkItem,该类是为网页和用户控件之间的通信而特别设计的,它定义每个链接所需的信息,如代码清单5-9所示。

代码清单5-9 HyperLinkControl.ascx.cs


public class HyperLinkItem

{

private string text;

private string url;

public string Text

{

get

{

return text;

}

set

{

text=value;

}

}

public string Url

{

get

{

return url;

}

set

{

url=value;

}

}

public HyperLinkItem()

{

}

public HyperLinkItem(string text, string url)

{

this.text=text;

this.url=url;

}

}


定义好HyperLinkItem类之后,就需要定义用户控件页面了。在这里,为了能够便于批量显示友情链接信息,将以前HyperLink控件换成了Label控件。代码如下所示:


<%@Control Language="C#"AutoEventWireup="true"

CodeBehind="HyperLinkControl.ascx.cs"

Inherits="_5_5.HyperLinkControl"%>

<asp:Panel

ID="Panel1"

runat="server"

EnableTheming="true"

Width="400px"

GroupingText="本站友情链接"

Font-Size="10pt">

<asp:Label

ID="MyHyperLink"

runat="server"></asp:Label>

</asp:Panel>


上面定义好HyperLinkItem类和用户控件页面之后,接下来考虑用户控件的代码隐藏类。在代码隐藏类中,定义了一个hyperLinkItems集合,它接受HyperLinkItem对象的数组,该数组的每一个元素都代表一个要在Label控件里显示的链接信息。如代码清单5-10所示。

代码清单5-10 HyperLinkControl.ascx.cs


public partial class HyperLinkControl:System.Web.UI.UserControl

{

protected void Page_Load(object sender, EventArgs e)

{

}

private HyperLinkItem[]hyperLinkItems;

public HyperLinkItem[]HyperLinkItems

{

get

{

return hyperLinkItems;

}

set

{

hyperLinkItems=value;

for(int i=0;i<HyperLinkItems.Length;i++)

{

MyHyperLink.Text+=

"<a href="+HyperLinkItems[i].Url+">"

+HyperLinkItems[i].Text+"</a>"

+"  ";

}

}

}

}


创建好用户控件之后,可以在宿主页面的后台代码里定义一个链接列表(可以定义文字或者图片链接),然后绑定到页面的HyperLinkControl用户控件并显示它。绑定完成之后,网页代码就不再和控件交互了。当用户单击某个链接时,它只是被直接转送到某个新地址而不需要任何额外的代码。如下面的代码所示:


public partial class TestHyperLinkControl:System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

HyperLinkItem[]items=new HyperLinkItem[4];

items[0]=new HyperLinkItem("本书官方网站",

"http://www. comesns.com/aspnet");

items[1]=new HyperLinkItem("百度搜索",

"http://www. baidu.com");

items[2]=new HyperLinkItem("谷歌搜索",

"http://www. google.cn");

items[3]=new HyperLinkItem("<img src=\"163.gif\"border=\"0px\"/>",

"http://www. 163.com");

HyperLinkControl1.HyperLinkItems=items;

}

}

}


运行上面的示例代码,结果如图5-5所示。

figure_0165_0118

图 5-5 TestHyperLinkControl.aspx运行结果

5.4.4 给用户控件添加事件

既然用户控件可以有自己的方法和属性,同样也可以有自己的事件。通过方法和属性,用户控件响应网页代码带来的变化。然而,使用事件时,刚好与方法和属性相反,用户控件通知网页发生了某个活动,然后网页代码做出响应。

有的时候,给用户控件添加一个事件往往可以解决大难题。例如一个登录用户控件,就可以给它添加一个自己的提交事件,这样当提交的时候可以方便地取得回传值,如用户名称和密码等信息。当然,给用户控件添加事件也不是那么麻烦。一般情况下,可以在用户控件文件(.ascx)中定义一个事件,然后在宿主页面文件(.aspx)中来订阅这个事件。当按钮被单击提交资料时,产生事件,发布通知,这样,宿主页面文件(.aspx)就可以收到信息并且进入处理这个事件的方法中。下面就使用创建一个登录用户控件的示例来演示这一过程。

代码清单5-11展示了一个简单的用户登录控件页面设计,它包含了两个文本框(一个用于输入用户名称,一个用于输入密码)和一个登录按钮。

代码清单5-11 LoginEventControl.ascx


<%@Control Language="C#"AutoEventWireup="true"

CodeBehind="LoginEventControl.ascx.cs"

Inherits="_5_6.LoginEventControl"%>

用户名称:

<asp:TextBox ID="userName"

runat="server"Width="100px"></asp:TextBox>

<p>

用户密码:

<asp:TextBox ID="password"

TextMode="Password"

runat="server"Width="100px">

</asp:TextBox>

</p>

<asp:Button ID="Login"

runat="server"Text="登录"

OnClick="Login_Click"/>


设计好用户控件页面之后,接下来需要创建一个自定义对象LoginEventControlArgs类,并在该类中添加一个EventMessage属性。该属性是一个只读属性,用来返回登录控件的相关用户名称和密码等信息。代码如下所示:


public class LoginEventControlArgs:EventArgs

{

private string userName;

private string password;

public string UserName

{

get

{

return userName;

}

set

{

userName=value;

}

}

public string Password

{

get

{

return password;

}

set

{

password=value;

}

}

//定义一个事件信息,用它来返回相关处理信息

public string EventMessage

{

get

{

return System.DateTime.Now.ToString()

+"产生登录事件:用户名称为:"

+userName+"——用户密码为:"

+password;

}

}

}


定义好自定义对象LoginEventControlArgs类之后,还需要创建一个代表LoginEvent事件签名的新委托。可以将该委托加在你喜欢的任何地方,但一般将它放在命名空间层次,仅在使用它声明类之前或者之后。如下面的代码所示:


//定义该事件使用的委托

public delegate void LoginEventControlHandler(

object sender, LoginEventControlArgs e);

public partial class LoginEventControl:

System.Web.UI.UserControl

{

//定义事件

public event LoginEventControlHandler LoginEvent;

protected void Page_Load(object sender, EventArgs e)

{

}

protected void Login_Click(object sender, EventArgs e)

{

//判断是否被订阅

if(LoginEvent!=null)

{

LoginEventControlArgs args=

new LoginEventControlArgs();

args.UserName=userName.Text;

args.Password=password.Text;

//事件发生

LoginEvent(this, args);

}

}

}


注意 引发一个事件时,首先必须检查事件的变量是否为空引用,如if(LoginEvent!=null)语句。如果变量为空引用,它表明还没有注册任何事件处理程序,有可能控件还没有创建。此时试图引发事件就会产生一个空引用异常。如果事件变量不为空,就可以使用名称并传递适当的一些事件参数来引发事件。

到目前为止,一个带自定义事件的登录用户控件就完成了。接下来,就可以通过在宿主页面TestLoginEventControl.aspx里面订阅该事件,从而来返回相关的登录处理信息。要在宿主页面订阅用户控件的自定义事件,首先,就得在用户控件引用的时候声明要订阅的用户控件事件,如声明一个OnLoginEvent事件。代码如下所示:


<MyLogin:LoginEventControl ID="LoginEventControl1"

OnLoginEvent="Login_LoginEvent"runat="server"/>


其次,需要在宿主页面的代码隐藏类里面订阅该事件,订阅方式如this.login.LoginEvent+=new LoginEventControlHandler(Login_LoginEvent)。之后,处理声明的用户控件事件OnLoginEvent的事件处理程序Login_LoginEvent。在Login_LoginEvent事件处理程序中,通过调用自定义对象LoginEventControlArgs的EventMessage属性来返回相关的登录信息。如下面的代码所示:


namespace_5_6

{

public partial class TestLoginEventControl:

System.Web.UI.Page

{

protected LoginEventControl login=

new LoginEventControl();

protected void Page_Load(object sender, EventArgs e)

{

this.Load+=new EventHandler(this.Page_Load);

//订阅用户控件的事件

this.login.LoginEvent+=

new LoginEventControlHandler(Login_LoginEvent);

}

//响应事件

protected void Login_LoginEvent(

object sender, LoginEventControlArgs e)

{

Response.Write(e.EventMessage);

}

}

}


运行宿主页面TestLoginEvent Control.aspx,当在页面的用户名称和用户密码文本框内添加好相关登录信息之后,单击“登录”按钮,将触发用户控件的OnLoginEvent事件,从而返回相应的登录处理信息。运行结果如图5-6所示。

figure_0168_0119

图 5-6 TestLoginEventControl.aspx运行结果

5.4.5 公开内部Web服务器控件

从上面的例子中不难发现这样一个重要问题,即用户控件里面包含的服务器控件只能够被用户控件自身访问,而不能够被宿主页面访问。也就是说,宿主页面不能够设置或者访问用户控件里的服务器控件的属性与方法,同样也不能够接收用户控件里的服务器控件的事件等。例如,在上面的登录用户控件中,宿主页面不能够访问它所使用的登录按钮和用户名、密码输入文本框。

当然,针对某些特殊的需要,可以采用给用户控件添加公有属性的方式来满足在宿主页面里设置用户控件内部服务器控件相关属性的需要。例如,在上面的登录用户控件中,假设我们希望能够在宿主页面里面调整用户密码文本输入控件password的TextMode属性,可以通过给用户控件添加一个TextMode属性来满足需要,如下面的代码所示:


public TextBoxMode TextMode

{

get

{

return password.TextMode;

}

set

{

password.TextMode=value;

}

}


在用户控件里面添加好属性之后,可以在该控件的宿主页面TestLoginEventControl.aspx里通过如下两种方式来进行设置。

第一种方式是在后台代码里面进行设置,如下面的代码所示:


this.LoginEventControl1.TextMode=TextBoxMode.Password;


第二种方式是直接在控件声明的时候进行设置,如下面的代码所示:


<MyLogin:LoginEventControl ID="LoginEventControl1"

TextMode="Password"OnLoginEvent="Login_LoginEvent"

runat="server"/>


通过这种添加属性的方法,可以将宿主页面里面的TextMode属性映射到登录用户控件里面的password文本输入控件的TextMode属性。这样的方式处理小部分属性还好,但如果需要向宿主页面公开一大部分属性,如在登录用户控件中公开所有的服务器控件属性等,这种方式就会变得单调乏味、不友好。而在这种情况下,就需要直接公开整个控件对象了,如公开登录用户控件里面的password文本输入控件里的所有属性,如下面的代码所示:


public TextBox Password

{

get

{

return password;

}

}


通过上面的方法公开整个控件对象之后,就可以直接在宿主页面的代码里面进行设置了。如图5-7所示,可以看见password文本输入控件里的所有对象,并进行设置。

例如,可以通过下面的方式来设置它的TextMode属性。如:


LoginEventControl1.Password.TextMode=TextBoxMode.Password


值得注意的是,通过这种方式便公开了内部控件的所有细节。也就是说,在宿主页面里面可以调用该控件的所有方法并可以接收到它的事件。这种方式带来了无限的灵活性,但同时也限制了代码的重用性。同时,它还增大了宿主页面与用户控件当前实现的内部细节紧密耦合的可能性,在修改或改进用户控件的时候很可能会破坏控件和使用它的网页的连接。因此,建议一般不要像这样去公开用户控件的所有属性。如有需要,可以通过上面的方法去创建专门的属性、方法和事件,并且只公开必需的功能,而不要为制造混乱提供机会。

figure_0169_0120

图 5-7 属性设置

5.4.6 以编程的方式动态加载用户控件

在实际开发中经常会遇见这种情况,要求系统根据某种条件来动态加载相关的用户控件,即从多个用户控件选择一个条件满足的用户控件加载到宿主页面。很显然,前面所用的直接在宿主页面上注册用户控件的方法在这里将不能够满足其需求,它需要用在后台,以编写程序代码的方式来加载条件符合的用户控件。

其实,页面用户控件的动态加载技术和普通Web服务器控件的动态加载技术相似,它们都需要调用Page.LoadControl()方法。与普通Web服务器控件的动态加载技术不同的是,加载用户控件调用Page.LoadControl()方法时,需要传递用户控件标记文件.ascx的文件名。Page.LoadControl()方法返回一个UserControl对象,可以把它添加到页面上并把它转换为特定的类型从而访问控件特定的功能。

一般情况下,可以在宿主页面使用容器控件或PlaceHolder控件来确保用户控件加载在你希望的位置。如下面的代码所示:


<asp:PlaceHolder ID="PlaceHolder1"

runat="server"></asp:PlaceHolder>


在页面设置好加载用户控件的容器之后,就可以在宿主页面的Page.Load事件里通过调用Page.LoadControl()方法来加载用户控件。之所以要在Page.Load事件里面加载用户控件,是因为这样你的用户控件才可以正确地重置它的状态并接收回发事件。还可以通过设置ID属性来给用户控件设置一个唯一的名称,有了这个用户控件的唯一名称,就可以在需要的时候借助于它通过Page.FindControl()方法获取对控件的引用。调用方法如下面的代码所示:


protected void Page_Load(object sender, EventArgs e)

{

LoadControl control=

((LadControl)Page.LoadControl("~/LoadControl.ascx");

control.ID="MyUserControl";

PlaceHolder1.Controls.Add(control);

}


值得注意的是,上面的示例采用了强制转换用户控件的方法来加载相关用户控件。除此之外,因为UserControl对象是由Control类派生出来的,因此还可以直接使用Control对象的引用指向Page.LoadControl()方法的返回值。如下面的代码所示:


protected void Page_Load(object sender, EventArgs e)

{

Control control=

Page.LoadControl("~/LoadControl.ascx");

control.ID="MyUserControl";

PlaceHolder1.Controls.Add(control);

}