模板化服务器控件示例
更新:2007 年 11 月
此示例演示一个名为 VacationHome 的控件,该控件演示如何实现模板化服务器控件。VacationHome 控件定义了两个公开的属性,即 Title 和 Caption 属性。网页设计器在设计时设置这些属性的值,而控件在运行时使用这些属性值为其子控件设置属性。通过在控件中编辑 <Template> 元素,页开发人员可以指定用于定义控件用户界面的控件和标记。利用该控件,页开发人员还可以使用 <#% Container %> 语法,因此在设计时可以在模板标记中引用 Title 和 Caption 值并将其显示在呈现的输出中。网页设计器可创建类似以下所示的 ASP.NET 网页:
<aspSample:VacationHome ID="VacationHome1"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting from $200"
Runat="server" Width="230px" Height="129px">
<Template>
<table bgcolor="aqua" align="center" id="Table1"
runat="server" style="width: 286px; height: 260px">
<tr>
<td style="width: 404px" align="center">
<asp:Label ID="Label1" Runat="server"
Text="<%#Container.Title%>"
Font-Names="Arial, Helvetica"></asp:Label>
</td>
</tr>
<tr>
<td style="width: 404px">
<asp:Image ID="Image1" Runat="server"
ImageUrl="~/images/hawaii.jpg" />
</td>
</tr>
<tr>
<td style="width: 404px; height: 26px;" align="center">
<asp:Label ID="Label2" Runat="server"
Text="<%#Container.Caption%>"
Font-Names="Arial, Helvetica">
</asp:Label>
</td>
</tr>
</table>
</Template>
</aspSample:VacationHome>
VacationHome 控件的代码清单
Option Strict On
Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Security.Permissions
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.Design
Namespace Samples.AspNet.VB.Controls
< _
AspNetHostingPermission(SecurityAction.Demand, _
Level:=AspNetHostingPermissionLevel.Minimal), _
AspNetHostingPermission(SecurityAction.InheritanceDemand, _
Level:=AspNetHostingPermissionLevel.Minimal), _
Designer(GetType(VacationHomeDesigner)), _
DefaultProperty("Title"), _
ToolboxData( _
"<{0}:VacationHome runat=""server""> </{0}:VacationHome>") _
> _
Public Class VacationHome
Inherits CompositeControl
Private _template As ITemplate
Private _owner As TemplateOwner
< _
Bindable(True), _
Category("Data"), _
DefaultValue(""), _
Description("Caption") _
> _
Public Overridable Property Caption() As String
Get
Dim s As String = CStr(ViewState("Caption"))
If s Is Nothing Then s = String.Empty
Return s
End Get
Set(ByVal value As String)
ViewState("Caption") = value
End Set
End Property
< _
Browsable(False), _
DesignerSerializationVisibility( _
DesignerSerializationVisibility.Hidden) _
> _
Public ReadOnly Property Owner() As TemplateOwner
Get
Return _owner
End Get
End Property
< _
Browsable(False), _
PersistenceMode(PersistenceMode.InnerProperty), _
DefaultValue(GetType(ITemplate), ""), _
Description("Control template"), _
TemplateContainer(GetType(VacationHome)) _
> _
Public Overridable Property Template() As ITemplate
Get
Return _template
End Get
Set(ByVal value As ITemplate)
_template = value
End Set
End Property
< _
Bindable(True), _
Category("Data"), _
DefaultValue(""), _
Description("Title"), _
Localizable(True) _
> _
Public Property Title() As String
Get
Dim s As String = CStr(ViewState("Title"))
If s Is Nothing Then s = String.Empty
Return s
End Get
Set(ByVal value As String)
ViewState("Title") = value
End Set
End Property
Protected Overrides Sub CreateChildControls()
Controls.Clear()
_owner = New TemplateOwner()
Dim temp As ITemplate = _template
If temp Is Nothing Then
temp = New DefaultTemplate
End If
temp.InstantiateIn(_owner)
Me.Controls.Add(_owner)
End Sub
Public Overrides Sub DataBind()
CreateChildControls()
ChildControlsCreated = True
MyBase.DataBind()
End Sub
End Class
<ToolboxItem(False)> _
Public Class TemplateOwner
Inherits WebControl
End Class
#Region "DefaultTemplate"
NotInheritable Class DefaultTemplate
Implements ITemplate
Sub InstantiateIn(ByVal owner As Control) _
Implements ITemplate.InstantiateIn
Dim title As New Label
AddHandler title.DataBinding, AddressOf title_DataBinding
Dim linebreak As New LiteralControl("<br/>")
Dim caption As New Label
AddHandler caption.DataBinding, _
AddressOf caption_DataBinding
owner.Controls.Add(title)
owner.Controls.Add(linebreak)
owner.Controls.Add(caption)
End Sub
Sub caption_DataBinding(ByVal sender As Object, _
ByVal e As EventArgs)
Dim source As Label = CType(sender, Label)
Dim container As VacationHome = _
CType(source.NamingContainer, VacationHome)
source.Text = container.Caption
End Sub
Sub title_DataBinding(ByVal sender As Object, _
ByVal e As EventArgs)
Dim source As Label = CType(sender, Label)
Dim container As VacationHome = _
CType(source.NamingContainer, VacationHome)
source.Text = container.Caption
End Sub
End Class
#End Region
Public Class VacationHomeDesigner
Inherits ControlDesigner
Public Overrides Sub Initialize(ByVal Component As IComponent)
MyBase.Initialize(Component)
SetViewFlags(ViewFlags.TemplateEditing, True)
End Sub
Public Overloads Overrides Function GetDesignTimeHtml() As String
Return "<span>This is design-time HTML</span>"
End Function
Public Overrides ReadOnly Property TemplateGroups() As TemplateGroupCollection
Get
Dim collection As New TemplateGroupCollection
Dim group As TemplateGroup
Dim template As TemplateDefinition
Dim control As VacationHome
control = CType(Component, VacationHome)
group = New TemplateGroup("Item")
template = New TemplateDefinition(Me, "Template", control, "Template", True)
group.AddTemplateDefinition(template)
collection.Add(group)
Return collection
End Get
End Property
End Class
End Namespace
// VacationHome.cs
using System;
using System.ComponentModel;
using System.Drawing;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;
namespace Samples.AspNet.CS.Controls
{
[
AspNetHostingPermission(SecurityAction.InheritanceDemand,
Level=AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.Demand,
Level = AspNetHostingPermissionLevel.Minimal),
Designer(typeof(VacationHomeDesigner)),
DefaultProperty("Title"),
ToolboxData(
"<{0}:VacationHome runat=\"server\"> </{0}:VacationHome>"),
]
public class VacationHome : CompositeControl
{
private ITemplate templateValue;
private TemplateOwner ownerValue;
[
Bindable(true),
Category("Data"),
DefaultValue(""),
Description("Caption")
]
public virtual string Caption
{
get
{
string s = (string)ViewState["Caption"];
return (s == null) ? String.Empty : s;
}
set
{
ViewState["Caption"] = value;
}
}
[
Browsable(false),
DesignerSerializationVisibility(
DesignerSerializationVisibility.Hidden)
]
public TemplateOwner Owner
{
get
{
return ownerValue;
}
}
[
Browsable(false),
PersistenceMode(PersistenceMode.InnerProperty),
DefaultValue(typeof(ITemplate), ""),
Description("Control template"),
TemplateContainer(typeof(VacationHome))
]
public virtual ITemplate Template
{
get
{
return templateValue;
}
set
{
templateValue = value;
}
}
[
Bindable(true),
Category("Data"),
DefaultValue(""),
Description("Title"),
Localizable(true)
]
public virtual string Title
{
get
{
string s = (string)ViewState["Title"];
return (s == null) ? String.Empty : s;
}
set
{
ViewState["Title"] = value;
}
}
protected override void CreateChildControls()
{
Controls.Clear();
ownerValue = new TemplateOwner();
ITemplate temp = templateValue;
if (temp == null)
{
temp = new DefaultTemplate();
}
temp.InstantiateIn(ownerValue);
this.Controls.Add(ownerValue);
}
public override void DataBind()
{
CreateChildControls();
ChildControlsCreated = true;
base.DataBind();
}
}
[
ToolboxItem(false)
]
public class TemplateOwner : WebControl
{
}
#region DefaultTemplate
sealed class DefaultTemplate : ITemplate
{
void ITemplate.InstantiateIn(Control owner)
{
Label title = new Label();
title.DataBinding += new EventHandler(title_DataBinding);
LiteralControl linebreak = new LiteralControl("<br/>");
Label caption = new Label();
caption.DataBinding
+= new EventHandler(caption_DataBinding);
owner.Controls.Add(title);
owner.Controls.Add(linebreak);
owner.Controls.Add(caption);
}
void caption_DataBinding(object sender, EventArgs e)
{
Label source = (Label)sender;
VacationHome container =
(VacationHome)(source.NamingContainer);
source.Text = container.Caption;
}
void title_DataBinding(object sender, EventArgs e)
{
Label source = (Label)sender;
VacationHome container =
(VacationHome)(source.NamingContainer);
source.Text = container.Title;
}
}
#endregion
public class VacationHomeDesigner : ControlDesigner
{
public override void Initialize(IComponent Component)
{
base.Initialize(Component);
SetViewFlags(ViewFlags.TemplateEditing, true);
}
public override string GetDesignTimeHtml()
{
return "<span>This is design-time HTML</span>";
}
public override TemplateGroupCollection TemplateGroups
{
get {
TemplateGroupCollection collection = new TemplateGroupCollection();
TemplateGroup group;
TemplateDefinition template;
VacationHome control;
control = (VacationHome)Component;
group = new TemplateGroup("Item");
template = new TemplateDefinition(this, "Template", control, "Template", true);
group.AddTemplateDefinition(template);
collection.Add(group);
return collection;
}
}
}
}
代码讨论
模板化控件添加了 ITemplate 类型的属性,并为控件定义了命名容器,从而扩展了 CompositeControl。通过定义命名容器,使得页开发人员在模板定义中可以使用 <#%Container%> 语法。模板控件还定义了一个从 Control 派生的某类型的属性,以承载模板中定义的控件。同时实现了特定属性和成员重写,以协调模板属性、宿主控件和命名容器的行为。
下面的列表以 VacationHome 为例,总结了模板化控件的主要实现要求。列表后的讨论中提供了有关每项要求的详细信息。VacationHome 控件演示以下内容:
从 CompositeControl 基类派生。模板化控件是特殊类型的复合控件。也可以从 WebControl 派生出模板化控件,但 CompositeControl 添加了 INamingContainer 的实现,因而允许使用 <#%Container%> 语法。
实现 ITemplate 类型的属性,并对其应用相关的元数据属性,以定义其持久性和命名容器。
公开 Control 类型的属性,或公开从 Control 派生的类(此类用于承载模板元素中定义的控件)。此控件被称为模板容器。
重写 CreateChildControls 方法,以实例化模板容器的 Controls 集合中的模板控件。
也可以定义一个供控件使用的默认模板,以在页开发人员没有指定模板时使用。
或者,还可以为控件定义一个设计器类。利用设计器类,页开发人员可以在可视化设计器中编辑模板。
应用于 ITemplate 属性 (Property) 的属性 (Attribute) 有 BrowsableAttribute、PersistenceModeAttribute 和 TemplateContainerAttribute。TemplateContainerAttribute 指定在解析表达式(如模板中的 <#%Container.Title%>)中的 Container 变量时页分析器应当使用的控件类型。指定的类型必须实现 INamingContainer,并定义控件的数据属性(在此例中为 Caption 和 Title)。此类型可以是模板所有者类型,也可以是控件甚至控件树类型。在 VacationHome 控件中,其类型传入 TemplateContainerAttribute 构造函数的控件并不是模板所有者,而是 VacationHome 控件本身。因为通常模板都不在可视化设计器的属性编辑窗口中编辑,所以 BrowsableAttribute 被设置为 false。而由于模板规范是作为控件的内部元素编写的,所以 PersistenceModeAttribute 被设置为 InnerProperty。
模板化控件必须定义一个 Control 类型的属性,该类型将成为模板所创建控件的容器。在此示例中,VacationHome 控件定义了 Owner 属性,其类型为 TemplateOwner,而该类型派生自 WebControl。TemplateOwner 类带有 ToolboxItem(false) 标记,指示在可视化设计器中 TemplateOwner 类不需要工具箱支持。有关更多信息,请参见 ToolboxItemAttribute。将实例化模板中的控件,并添加到 Owner 控件的 Controls 属性。如果您的控件公开了多个 ITemplate 属性,则可能要为每个模板定义一个单独的模板容器属性。Owner 属性作为公共属性公开。这样便允许页设计人员在运行时使用 FindControl 方法来引用模板中的特定控件。
VacationHome 控件重写了 CreateChildControls 基方法。CreateChildControls 方法实例化 Template 属性中指定的控件,并将其添加到 Owner 对象的 Controls 集合中。随后将 Owner 对象添加到 VacationHome 实例的 Controls 集合中,之后就可以呈现控件了。
如果页开发人员没有定义模板,VacationHome 便会创建 DefaultTemplate 的一个实例,该模板派生自 ITemplate。InstantiateIn 方法创建两个 Label 控件,以显示 Title 和 Caption 属性。为每个控件的 DataBinding 事件创建事件处理程序方法。DataBinding 事件处理程序将 Text 属性设置为 VacationHome 的适当属性(Title 或 Caption)。
为 VacationHome 类实现设计器的 VacationHomeDesigner 类派生自 ControlDesigner。在初始化过程中,用 TemplateEditing 调用的 SetViewFlags 方法将启用设计时模板编辑。重写 GetDesignTimeHtml 方法,以在控件未处于模板编辑模式时呈现控件。重写后的 TemplateGroups 属性中的代码定义了一个包含一个模板的模板组。每个 TemplateGroup 对象都在可视化设计器的模板编辑用户界面中添加一项模板编辑选择。(在 Visual Studio 2005 中,模板编辑选择以与控件关联的智能标记的形式显示。)在 VacationHome 控件中只有一个编辑选择,那就是“Item”。每个 TemplateDefinition 对象都创建一个模板,以供在设计器中编辑之用。TemplateDefinition 构造函数的 templatePropertyName 参数指定控件中模板属性的名称。对 VacationHome 类应用 DesignerAttribute,以指定设计器类。
VacationHome 控件的测试页
下面的示例演示一个使用 VacationHome 控件的 .aspx 页。页中该控件的第一个实例为其 ITemplate 属性指定一个模板。第二个实例没有指定 ITemplate 属性,这使得 VacationHome 控件在运行时使用其默认的模板。
<%@ Page Language="VB"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
If Not IsPostBack Then
VacationHome1.DataBind()
VacationHome2.DataBind()
End If
End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>
VacationHome Control Test Page
</title>
</head>
<body>
<form id="form1" runat="server">
<aspSample:VacationHome ID="VacationHome1"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting $200"
Runat="server" Width="230px" Height="129px">
<Template>
<table id="TABLE1" runat="server"
style="width: 286px; height: 260px;
background-color:Aqua; text-align:center">
<tr>
<td style="width: 404px" align="center">
<asp:Label ID="Label1" Runat="server"
Text="<%#Container.Title%>"
Font-Names="Arial, Helvetica"></asp:Label>
</td>
</tr>
<tr>
<td style="width: 404px">
<asp:Image ID="Image1" Runat="server"
ImageUrl="~/images/hawaii.jpg"
AlternateText="Hawaii home" />
</td>
</tr>
<tr>
<td style="width: 404px; height: 26px;" align="center">
<asp:Label ID="Label2" Runat="server"
Text="<%#Container.Caption%>"
Font-Names="Arial, Helvetica">
</asp:Label>
</td>
</tr>
</table>
</Template>
</aspSample:VacationHome>
<br /> <br />
<br />
The VacationHome control rendered with its default template:
<br /> <br />
<aspSample:VacationHome ID="VacationHome2"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting $200"
Runat="server" BorderStyle="Solid" BackColor="#66ffff"
Height="30px" Width="238px" Font-Names="Arial, Helvetica" />
</form>
</body>
</html>
<%@ Page Language="C#"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
VacationHome1.DataBind();
VacationHome2.DataBind();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>
VacationHome Control Test Page
</title>
</head>
<body>
<form id="form1" runat="server">
<aspSample:VacationHome ID="VacationHome1"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting $200"
Runat="server" Width="230px" Height="129px">
<Template>
<table id="TABLE1" runat="server"
style="width: 286px; height: 260px;
background-color:Aqua; text-align:center">
<tr>
<td style="width: 404px" align="center">
<asp:Label ID="Label1" Runat="server"
Text="<%#Container.Title%>"
Font-Names="Arial, Helvetica"></asp:Label>
</td>
</tr>
<tr>
<td style="width: 404px">
<asp:Image ID="Image1" Runat="server"
ImageUrl="~/images/hawaii.jpg"
AlternateText="Hawaii home" />
</td>
</tr>
<tr>
<td style="width: 404px; height: 26px;" align="center">
<asp:Label ID="Label2" Runat="server"
Text="<%#Container.Caption%>"
Font-Names="Arial, Helvetica">
</asp:Label>
</td>
</tr>
</table>
</Template>
</aspSample:VacationHome>
<br /> <br />
<br />
The VacationHome control rendered with its default template:
<br /> <br />
<aspSample:VacationHome ID="VacationHome2"
Title="Condo for Rent in Hawaii"
Caption="Ocean view starting $200"
Runat="server" BorderStyle="Solid" BackColor="#66ffff"
Height="30px" Width="238px" Font-Names="Arial, Helvetica" />
</form>
</body>
</html>
生成和使用示例
有关生成控件和在页中使用该控件的信息,请参见生成自定义服务器控件示例。必须添加对 System.Design 程序集的引用才能进行编译。