除了 ASP.NET 提供的内置服务器控件之外,还可以使用已学会的编写 Web 窗体页的相同编程技巧轻松地定义自己的控件。实际上,只需少量修改,几乎任何 Web 窗体页都可在其他页中作为服务器控件重用(注意,用户控件的类型是
System.Web.UI.TemplateParser,该类型直接从
System.Web.UI.UserControl 继承)。用作服务器控件的 Web 窗体页简称为用户控件。作为约定,使用 .ascx 扩展名指示这样的控件。这样可以确保用户控件的文件不能作为独立的 Web 窗体页执行(您会略微了解到在用户控件和 Web 窗体页之间有很少但很重要的差异)。用户控件通过
Register 指令包含在 Web 窗体页中。
除了将公共字段提升为控件属性外,还可以使用属性语法。属性语法具有能够在设置或检索属性时执行代码的优点。下面的示例演示 Address 用户控件,该控件在内部包装了 TextBox 控件的文本属性。这样做的好处在于控件可以无偿继承 TextBox 控件的自动状态管理。
注意,在包含 Web 窗体页上有两个 Address 用户控件,它们的 Caption 属性分别设置为“Billing Address”和“Shipping Address”。用户控件的真正威力在于这种可重用性。
另一个有用的用户控件是用于收集用户名和密码的 Login 控件。
在本示例中,窗体验证控件被添加到 Login 用户控件。
<script language="JavaScript" type="text/javascript"> function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i < numTabs; i++) { document.all("tab" + id, (index + i) % numTabs).className = "backtab"; } document.all("code" + id, index).style.display = ""; for (var j=1; j < numTabs; j++) { document.all("code" + id, (index + j) % numTabs).style.display = "none"; } } </script>
用户控件的类型由 Control 指令的 ClassName 属性确定。例如,以文件名“userctrl7.ascx”保存的用户控件被指定为强类型“UserCtrl7”,如下所示:
重要事项 仅当用户控件包含 Register 指令时(即使没有实际声明任何用户控件标记),用户控件的强类型才能由包含 Web 窗体页使用。
<%@ Register TagPrefix="Acme" TagName="Message" Src="pagelet1.ascx" %>TagPrefix 确定用户控件的唯一命名空间(以便多个同名的用户控件可以相互区分)。 TagName 是用户控件的唯一名称(可以选择任何名称)。 Src 属性是用户控件的虚拟路径 -- 如“MyPagelet.ascx”或“/MyApp/Include/MyPagelet.ascx”。注册了用户控件后,可以像放置普通的服务器控件那样,将用户控件标记放置在 Web 窗体页中(包括 runat="server" 属性):
<Acme:Message runat="server"/>下面的示例演示导入到另一 Web 窗体页中的用户控件。注意,本例中的用户控件只是一个简单的静态文件。
公开用户控件属性
当 Web 窗体页被视为控件时,该 Web 窗体的公共字段和方法也被提升为该控件的公共属性 (Property) (即标记属性 (Attribute))和方法。下面的示例显示前一用户控件示例的扩展,添加了两个公共 String 字段。注意,这些字段可以在包含页中以声明方式或编程方式设置。<%@ Register TagPrefix="Acme" TagName="Message" Src="userctrl2_cs.ascx" %> <html> <script language="C#" runat="server"> void SubmitBtn_Click(Object sender, EventArgs E) { MyMessage.Text = "Message text changed!"; MyMessage.Color = "red"; } </script> <body style="font: 10pt verdana"> <h3>A Simple User Control w/ Properties</h3> <form runat="server"> <Acme:Message id="MyMessage" Text="This is a custom message!" Color="blue" runat="server"/> <p> <asp:button text="Change Properties" OnClick="SubmitBtn_Click" runat=server/> </form> </body> </html> 用户控件代码:
<script language="C#" runat="server"> public String Color = "blue"; public String Text = "This is a simple message user control!"; </script> <span id="Message" style="color:<%=Color%>"><%=Text%></span>
除了将公共字段提升为控件属性外,还可以使用属性语法。属性语法具有能够在设置或检索属性时执行代码的优点。下面的示例演示 Address 用户控件,该控件在内部包装了 TextBox 控件的文本属性。这样做的好处在于控件可以无偿继承 TextBox 控件的自动状态管理。
注意,在包含 Web 窗体页上有两个 Address 用户控件,它们的 Caption 属性分别设置为“Billing Address”和“Shipping Address”。用户控件的真正威力在于这种可重用性。
<%@ Register TagPrefix="Acme" TagName="Address" Src="userctrl3_cs.ascx" %> <html> <script language="C#" runat="server"> void SubmitBtn_Click(Object sender, EventArgs E) { MyLabel.Text += "<b>Shipping Address:</b> " + ShipAddr.Address + ", " + ShipAddr.City + ", " + ShipAddr.State + ", " + ShipAddr.Zip + "<br>"; MyLabel.Text += "<b>Billing Address:</b> " + BillAddr.Address + ", " + BillAddr.City + ", " + BillAddr.State + ", " + BillAddr.Zip + "<br>"; } </script> <body style="font: 10pt verdana"> <h3>A Simple User Control w/ Properties</h3> <form runat="server"> <Acme:Address id="ShipAddr" Caption="Shipping Address" Address="One Microsoft Way" City="Redmond" State="WA" Zip="98052" runat="server"/> <p> <Acme:Address id="BillAddr" Caption="Billing Address" runat="server"/> <p> <asp:button Text="Submit Form" OnClick="SubmitBtn_Click" runat=server/> </form> <asp:Label id="MyLabel" runat="server"/> </body> </html> 用户控件代码:
<script language="C#" runat="server"> public String Caption = "Address"; public String Address { get { return TxtAddress.Value; } set { TxtAddress.Value = value; } } public String City { get { return TxtCity.Value; } set { TxtCity.Value = value; } } public String State { get { return TxtState.Value; } set { TxtState.Value = value; } } public String Zip { get { return TxtZip.Value; } set { TxtZip.Value = value; } } </script> <table style="font: 10pt verdana"> <tr> <td colspan="6" style="padding-bottom:10"> <b><%=Caption%></b> </td> </tr> <tr> <td> Address: </td> <td colspan="5"> <input id="TxtAddress" size="50" type="text" runat="server"> </td> </tr> <tr> <td> City: </td> <td> <input id="TxtCity" type="text" runat="server"> </td> <td> State: </td> <td> <input id="TxtState" size="2" type="text" runat="server"> </td> <td> Zip: </td> <td> <input id="TxtZip" size="5" type="text" runat="server"> </td> </tr> </table>
另一个有用的用户控件是用于收集用户名和密码的 Login 控件。
<%@ Register TagPrefix="Acme" TagName="Login" Src="userctrl4_cs.ascx" %> <html> <script language="C#" runat="server"> void Page_Load(Object sender, EventArgs E) { if (Page.IsPostBack) { MyLabel.Text += "The UserId is " + MyLogin.UserId + "<br>"; MyLabel.Text += "The Password is " + MyLogin.Password + "<br>"; } } </script> <body style="font: 10pt verdana"> <h3>A Login User Control</h3> <form runat="server"> <Acme:Login id="MyLogin" UserId="John Doe" Password="Secret" BackColor="beige" runat="server"/> </form> <asp:Label id="MyLabel" runat="server"/> </body> </html> 用户控件代码:
<script language="C#" runat="server"> public String BackColor = "white"; public String UserId { get { return User.Text; } set { User.Text = value; } } public String Password { get { return Pass.Text; } set { Pass.Text = value; } } </script> <table style="background-color:<%=BackColor%>;font: 10pt verdana;border-width:1;border-style:solid;border-color:black;" cellspacing=15> <tr> <td><b>Login: </b></td> <td><ASP:TextBox id="User" runat="server"/></td> </tr> <tr> <td><b>Password: </b></td> <td><ASP:TextBox id="Pass" TextMode="Password" runat="server"/></td> </tr> <tr> <td></td> <td><ASP:Button Text="Submit" runat="server"/></td> </tr> </table>
在本示例中,窗体验证控件被添加到 Login 用户控件。
<%@ Register TagPrefix="Acme" TagName="Login" Src="userctrl5_cs.ascx" %> <html> <script language="C#" runat="server"> public void Page_Load(Object sender, EventArgs E) { if (Page.IsPostBack) { Page.Validate(); if (Page.IsValid) { MyLabel.Text += "The UserId is " + MyLogin.UserId + "<br>"; MyLabel.Text += "The Password is " + MyLogin.Password + "<br>"; } } } </script> <body style="font: 10pt verdana"> <h3>A Login User Control</h3> <form runat="server"> <Acme:Login id="MyLogin" BackColor="beige" runat="server"/> </form> <asp:Label id="MyLabel" runat="server"/> </body> </html> 用户控件代码:
get { return Page.IsValid; } } </script> <table style="background-color:<%=BackColor%>;font: 10pt verdana;border-width:1;border-style:solid;border-color:black;" cellspacing=15> <tr> <td><b>Login: </b></td> <td><ASP:TextBox id="User" runat="server"/></td> </tr> <tr> <td><b>Password: </b></td> <td><ASP:TextBox id="Pass" TextMode="Password" runat="server"/></td> </tr> <tr> <td></td> <td><ASP:Button Text="Submit" runat="server"/></td> </tr> <tr> <td align="center" valign="top" colspan="2"> <asp:RegularExpressionValidator id="Validator1" ControlToValidate="Pass" ValidationExpression="[0-9a-zA-Z]{5,}" Display="Dynamic" Font-Size="8pt" runat=server> Password must be >= 5 alphanum chars<br> </asp:RegularExpressionValidator> <asp:RequiredFieldValidator id="Validator2" ControlToValidate="User" Font-Size="8pt" Display="Dynamic" runat=server> UserId cannot be blank<br> </asp:RequiredFieldValidator> <asp:RequiredFieldValidator id="Validator3" ControlToValidate="Pass" Font-Size="8pt" Display="Dynamic" runat=server> Password cannot be blank<br> </asp:RequiredFieldValidator> </td> </tr> </table>
在用户控件中封装事件
用户控件参与请求的整个执行生存期,方式与普通的服务器控件类似。这意味着用户控件可以处理自己的事件,封装来自包含 Web 窗体页的某些页逻辑。下面的示例演示一个在内部处理自己的回发的产品清单用户控件。请注意,该用户控件本身没有包装 <form runat="server"> 控件。由于一页上只能有一个窗体控件(ASP.NET 不允许嵌套的服务器窗体),因此需要包含 Web 窗体页负责定义该控件。<%@ Page Language="C#" %> <%@ Register TagPrefix="Acme" TagName="BookList" Src="userctrl6_cs.ascx" %> <html> <body style="font: 10pt verdana"> <h3>A User Control w/ an Event</h3> <form runat="server"> <Acme:BookList runat="server" id="BookList1" /> </form> </body> </html> 用户控件代码:
<%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <script language="C#" runat="server"> void Page_Load(Object Src, EventArgs e) { if (!Page.IsPostBack) { SqlConnection myConnection = new SqlConnection("server=(local)//SQLExpress;database=pubs;Integrated Security=SSPI"); SqlDataAdapter myCommand = new SqlDataAdapter("select * from Titles where type='" + Category.SelectedItem.Value + "'", myConnection); DataSet ds = new DataSet(); myCommand.Fill(ds, "Titles"); MyDataList.DataSource = ds.Tables["Titles"].DefaultView; MyDataList.DataBind(); } } void Category_Select(Object sender, EventArgs e) { SqlConnection myConnection = new SqlConnection("server=(local)//SQLExpress;database=pubs;Integrated Security=SSPI"); SqlDataAdapter myCommand = new SqlDataAdapter("select * from Titles where type='" + Category.SelectedItem.Value + "'", myConnection); DataSet ds = new DataSet(); myCommand.Fill(ds, "Titles"); MyDataList.DataSource = ds.Tables["Titles"].DefaultView; MyDataList.DataBind(); } </script> <table style="font: 10pt verdana"> <tr> <td><b>Select a Category:</b></td> <td style="padding-left:15"> <ASP:DropDownList AutoPostBack="true" id="Category" OnSelectedIndexChanged="Category_Select" runat="server"> <ASP:ListItem value="business">Business</ASP:ListItem> <ASP:ListItem value="trad_cook">Traditional Cooking</ASP:ListItem> <ASP:ListItem value="mod_cook">Modern Cooking</ASP:ListItem> </ASP:DropDownList> </td> </tr> </table> <ASP:DataList id="MyDataList" BorderWidth="0" RepeatColumns="2" runat="server"> <ItemTemplate> <table cellpadding=10 style="font: 10pt verdana"> <tr> <td valign="top"> <img align="top" src='<%# DataBinder.Eval(Container.DataItem, "title_id", "images/title-{0}.gif") %>' > </td> <td valign="top"> <b>Title: </b><%# DataBinder.Eval(Container.DataItem, "title") %><br> <b>Category: </b><%# DataBinder.Eval(Container.DataItem, "type") %><br> <b>Publisher ID: </b><%# DataBinder.Eval(Container.DataItem, "pub_id") %><br> <b>Price: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %> </td> </tr> </table> </ItemTemplate> </ASP:DataList>
以编程方式创建用户控件
与普通服务器控件一样,用户控件也可以以编程方式创建。通过传入用户控件源文件的虚拟路径,可使用页的 LoadControl 方法加载用户控件:<script language="JavaScript" type="text/javascript"> function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i < numTabs; i++) { document.all("tab" + id, (index + i) % numTabs).className = "backtab"; } document.all("code" + id, index).style.display = ""; for (var j=1; j < numTabs; j++) { document.all("code" + id, (index + j) % numTabs).style.display = "none"; } } </script>
C# | VB | |
Control c1 = LoadControl("userctrl7.ascx"); ((UserCtrl7)c1).Category = "business"; Page.Controls.Add(c1); Dim c1 As Control = LoadControl("userctrl7.ascx") CType(c1, (UserCtrl7)).Category = "business" Page.Controls.Add(c1) |
用户控件的类型由 Control 指令的 ClassName 属性确定。例如,以文件名“userctrl7.ascx”保存的用户控件被指定为强类型“UserCtrl7”,如下所示:
<%@ Control ClassName="UserCtrl7" %>由于 LoadControl 方法返回 System.Web.UI.Control 类型,因此必须将其转换为适当的强类型以便设置控件的各个属性。最后,用户控件被添加到基页的 ControlCollection。
<%@ Register TagPrefix="Acme" TagName="BookList" Src="userctrl7_cs.ascx" %> <html> <script language="C#" runat="server"> void Page_Load(Object sender, EventArgs E) { Page.Controls.Add(new HtmlGenericControl("hr")); Control c1 = LoadControl("userctrl7_cs.ascx"); ((UserCtrl7CS)c1).Category = "business"; Page.Controls.Add(c1); Page.Controls.Add(new HtmlGenericControl("hr")); Control c2 = LoadControl("userctrl7_cs.ascx"); ((UserCtrl7CS)c2).Category = "trad_cook"; Page.Controls.Add(c2); Page.Controls.Add(new HtmlGenericControl("hr")); Control c3 = LoadControl("userctrl7_cs.ascx"); ((UserCtrl7CS)c3).Category = "mod_cook"; Page.Controls.Add(c3); } </script> <body style="font: 10pt verdana"> <h3>Creating User Controls Programmatically</h3> </body> </html> 用户控件代码:
<%@ Control ClassName="UserCtrl7CS" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <script language="C#" runat="server"> private String _category = ""; public String Category { get { return _category; } set { _category = value; SqlConnection myConnection = new SqlConnection("server=(local)//SQLExpress;database=pubs;Integrated Security=SSPI"); SqlDataAdapter myCommand = new SqlDataAdapter("select * from Titles where type='" + _category + "'", myConnection); DataSet ds = new DataSet(); myCommand.Fill(ds, "Titles"); MyDataList.DataSource = ds.Tables["Titles"].DefaultView; MyDataList.DataBind(); } } </script> <span style="font: 12pt verdana">Category: <%=Category%></span> <ASP:DataList id="MyDataList" BorderWidth="0" RepeatColumns="2" runat="server"> <ItemTemplate> <table cellpadding=10 style="font: 10pt verdana"> <tr> <td valign="top"> <img align="top" src='<%# DataBinder.Eval(Container.DataItem, "title_id", "images/title-{0}.gif") %>' > </td> <td valign="top"> <b>Title: </b><%# DataBinder.Eval(Container.DataItem, "title") %><br> <b>Category: </b><%# DataBinder.Eval(Container.DataItem, "type") %><br> <b>Publisher ID: </b><%# DataBinder.Eval(Container.DataItem, "pub_id") %><br> <b>Price: </b><%# DataBinder.Eval(Container.DataItem, "price", "$ {0}") %> </td> </tr> </table> </ItemTemplate> </ASP:DataList>
重要事项 仅当用户控件包含 Register 指令时(即使没有实际声明任何用户控件标记),用户控件的强类型才能由包含 Web 窗体页使用。