Пример шаблонного серверного элемента управления

Обновлен: Ноябрь 2007

В данном примере приводится элемент управления 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;
            }
        }
    }

}

Рассмотрение кода

Шаблонный элемент управления расширяет элемент управления CompositeControl, добавляя свойство типа ITemplate и определяя контейнер именования для элемента управления. Определение контейнера именования позволяет конструктору страниц использовать синтаксис <#%Container%> в определении шаблона. В шаблонном элементе управления также определяется свойство типа, производного от Control, предназначенное для размещения элементов управления, определенных в шаблоне. Для координации свойства шаблона, ведущего элемента управления и поведения контейнера именования реализован ряд атрибутов и переопределений членов.

В приведенном ниже списке сведены общие требования к реализации шаблонного элемента управления на примере VacationHome. Подробные сведения о каждом требовании приведены в обсуждении после списка. Элемент управления VacationHome демонстрирует:

  • Наследование от базового класса CompositeControl. Шаблонный элемент управления — это особый тип составного элемента управления. Также можно использовать наследование от класса WebControl, но элемент управления CompositeControl добавляет реализацию интерфейса INamingContainer, которая допускает использование синтаксиса <#%Container%>.

  • Реализацию свойства типа ITemplate и применение к нему релевантных атрибутов метаданных для задания его свойства сохраняемости и контейнера именования.

  • Предоставление доступа к свойству типа Control или класса, производного от Control, которое служит для размещения элементов управления, определенных в шаблонном элементе. Данный элемент управления называется контейнером шаблона.

  • Переопределение метода CreateChildControls для создания экземпляров шаблонных элементов управления в коллекции Controls контейнера шаблона.

  • Определение шаблона по умолчанию (необязательно), который используется элементом управления, если разработчик страницы не указал шаблон.

  • Определение класса конструктора для элемента управления (необязательно). Класс конструктора позволяет разработчику страницы изменять шаблоны в визуальном конструкторе.

К свойству ITemplate применяются атрибуты BrowsableAttribute, PersistenceModeAttribute и TemplateContainerAttribute. Атрибут TemplateContainerAttribute задает тип элемента управления, который синтаксический анализатор страниц должен использовать при разрешении переменной Container в выражении внутри шаблона — например, <#%Container.Title%>. Указанный тип должен реализовывать интерфейс INamingContainer и определять свойства данных (в данном случае это Caption и Title) для элемента управления. Данный тип может быть типом владельца шаблона или элемента управления, находящегося выше в дереве элементов управления. В случае элемента управления VacationHome в качестве элемента управления, тип которого передается конструктору TemplateContainerAttribute, выступает не владелец шаблона, а сам элемент управления VacationHome. Атрибут BrowsableAttribute получает значение false, так как шаблоны обычно не редактируются в окне редактирования свойств визуального конструктора. Атрибут PersistenceModeAttribute получает значение InnerProperty, так как спецификация шаблона записывается в качестве элемента, внутреннего по отношению к элементу управления.

В шаблонном элементе управления должно определяться свойство типа Control, которое становится контейнером для элементов управления, создаваемых в шаблоне. В примере в элементе управления VacationHome определяется свойство Owner, которое относится к типу TemplateOwner, который, в свою очередь, является производным от WebControl. Класс TemplateOwner помечен атрибутом ToolboxItem(false) для обозначения того, что класс TemplateOwner не требует поддержки панели элементов в визуальном конструкторе. Дополнительные сведения см. в разделе ToolboxItemAttribute. Элементы управления в шаблоне создаются и добавляются в свойство Controls элемента управления Owner. Если элемент управления предоставляет доступ к нескольким свойствам ITemplate, то необходимо задавать отдельное свойство контейнера шаблона для каждого шаблона. К свойству Owner предоставляется общий доступ. Это позволяет конструктору страниц использовать метод FindControl для обращения к отдельным элементам управления в шаблоне во время выполнения.

Элемент управления VacationHome переопределяет базовый метод CreateChildControls. Метод CreateChildControls создает элементы управления, указанные в свойстве Template, и добавляет их в коллекцию Controls объекта Owner. Затем объект Owner добавляется в коллекцию Controls экземпляра VacationHome, после чего элемент управления может быть отрисован.

Если разработчик страницы не задал шаблон, то VacationHome создает экземпляр DefaultTemplate, который является производным от интерфейса ITemplate. Метод InstantiateIn создает два элемента управления Label для отображения свойств Title и Caption. Помимо этого, для события DataBinding каждого элемента управления создается метод-обработчик событий. Обработчик событий DataBinding присваивает свойству Text значение из соответствующего свойства (Title или Caption) элемента управления VacationHome.

Класс VacationHomeDesigner, который реализует визуальный конструктор для элемента управления VacationHome, является производным от ControlDesigner. В ходе инициализации метод SetViewFlags, вызванный с параметром TemplateEditing, разрешает изменение шаблона во время разработки. Метод GetDesignTimeHtml переопределяется, чтобы отрисовывать элемент управления, когда он не находится в режиме изменения шаблона. Код в переопределенном свойстве TemplateGroups определяет одну группу шаблонов, которая содержит один шаблон. Каждый объект TemplateGroup добавляет вариант изменения шаблона к пользовательскому интерфейсу визуального конструктора при изменении шаблонов (в Visual Studio 2005 команды изменения шаблонов отображаются в смарт-теге, связанном с элементом управления). В элементе управления VacationHome единственным вариантом изменения является «Элемент». Каждый объект TemplateDefinition создает шаблон для изменения в конструкторе. Параметр templatePropertyName конструктора TemplateDefinition указывает имя свойства шаблона в элементе управления. Атрибут DesignerAttribute применяется к классу VacationHome для указания класса визуального конструктора.

Тестовая страница для элемента управления VacationHome

В следующем примере показана страница ASPX, использующая элемент управления VacationHome. Первый экземпляр элемента управления на странице задает шаблон для свойства 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.

См. также

Основные понятия

Пример составного веб-элемента управления

Типизированные стили для дочерних элементов управления

Другие ресурсы

Разработка пользовательских серверных элементов управления ASP.NET