• Элементы управления пользователя
  • Простой элемент управления пользователя
  • Преобразование приложения предварительного заказа мероприятия в элемент управления пользователя
  • Специальные элементы управления
  • Конфигурация проекта специального элемента управления
  • Базовые специальные элементы управления
  • Производный элемент управления RainbowLabel
  • Поддержание состояния в специальном элементе управления
  • Создание композитного специального элемента управления
  • Элемент управления выборочным опросом
  • Элемент управления Option
  • Построитель элемента управления StrawPoll
  • Стиль StrawPoll
  • Элемент управления StrawPoll
  • Добавление обработчика событий
  • Заключение
  • Глава 18

    Специальные элементы управления

    При разработке приложений Web случаются ситуации, когда доступные мощные инструменты разработки не вполне соответствуют требованиям для определенного проекта. Возможно, что заданный элемент управления работает не вполне так, как это требуется, или раздел кода, предназначенный для повторного использования на нескольких страницах, слишком сложен в руках нескольких разработчиков. В таких ситуациях существуют серьезные доводы в пользу специальных элементов управления. Специальные элементы управления могут, в самом простейшем виде объединить несколько существующих элементов управления вместе, возможно, с дополнительными свойствами, определяющими компоновку, или могут полностью отличаться от любых существующих элементов управления. Использование специального элемента управления настолько же просто, как и использование любого другого элемента управления в ASP.NET, что, конечно, облегчает кодирование web-сайтов.

    В прошлом было сложно реализовывать такие специально созданные элементы управления, особенно в крупномасштабных системах, где требовались сложные процедуры регистрации для их использования. Даже в простых системах требования кодирования для создания специального элемента управления могли существенно усложнить процесс. Языки сценариев старых языков разработки Web также страдали от отсутствия возможности предоставить полноценный доступ к искусно созданной объектной модели, что приводило к низкой производительности в целом.

    Платформа .NET предоставляет идеальное окружение для создания специальных элементов управления. Динамическое обнаружение сборок, что свойственно системе .NET делает установку на новом сервере Web посредством копирования структуры каталогов, содержащей код, вместе со всеми используемыми DLL. Кроме того, специальное внимание было уделено упрощению создания своих собственных элементов управления с помощью простых технологий программирования.

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

    □ Элементы управления пользователя — преобразование существующих страниц ASP.NET в элементы управления

    □ Специальные элементы управления — объединение функций нескольких элементов управления, расширение существующих элементов управления и создание новых элементов управления с самого начала 

    Мы проиллюстрируем элементы управления пользователя, преобразовывая приложение предварительного заказа помещения для проведения мероприятий из предыдущей главы в элемент управления пользователя так, чтобы его можно было легко встроить в другие страницы ASP.NET. В случае специальных элементов управления мы создадим элемент управления для выборочного опроса общественного мнения, позволяющий пользователю голосовать за какой-то вариант из списка и видеть, как проходит голосование. 

    Элементы управления пользователя

    Элементы управления пользователя создаются с помощью кода ASP.NET также, как создаются стандартные страницы Web ASP.NET. Различие состоит в том, что после создания элемента управления пользователя его можно повторно применять на множестве страниц ASP.NET с минимальными трудностями.

    Например, предположим, что создана страница, которая выводит некоторую информацию из базы данных, возможно, информацию о заказе. Вместо создания фиксированной страницы, которая это делает, можно поместить соответствующий код в элемент управления пользователя и затем вставлять при желании этот элемент управления в множество различных страниц Web.

    Кроме того, можно определить свойства и методы для элементов управления пользователя. Например, определить фоновый цвет для вывода таблицы базы данных на странице Web или повторно выполнить запрос к базе данных для проверки изменений.

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

    Простой элемент управления пользователя

    В VS.NET создадим новое приложение Web с именем

    PCSUserCWebAppl
    , открывая VS.NET, щелкнув на Getting Started, и выбирая затем New Project, щелкнув на пиктограмме Web Application. Должно открыться диалоговое окно, позволяющее сохранить этот проект.

    Когда будут созданы стандартные файлы, выберем пункт меню Project|Add New Item… и добавим Web User Control с именем

    PCSUserC1.ascx
    , как показано ниже:

    Добавленные к проекту файлы с расширениями

    .ascx
    и
    .ascx.cs
    работают очень похожим образом с файлами
    .aspx
    , с которыми мы уже знакомы. Файл
    .ascx
    будет содержать код ASP.NET и выглядеть очень похоже на обычный файл
    .aspx
    . Файл
    .ascx.cs
    является нашим кодом, который определяет элемент управления пользователя преимущественно так же, как в файлах
    .aspx.cs
    определяются формы.

    Файлы

    .ascx
    можно просматривать в виде кода HTML или в окне построителя, как и файлы
    .aspx
    . Просмотр файла в виде кода HTML открывает важное различие: в элементе
    <body>
    отсутствует элемент <form>. Это связано с тем, что элементы управления пользователя будут вставляться внутрь форм ASP.NET в других файлах, и поэтому не нуждаются в собственном теге формы.

    Просмотр созданных шаблонных файлов открывает еще одно важное отличие: созданный класс наследует из класса

    System.Web.UI.UserControl
    . Это также связано с тем, что элемент управления будет использоваться внутри формы.

    Наш простой элемент управления будет выводить графическое изображение, соответствующее одной из четырех стандартных мастей колоды карт (трефы, бубны, черви, пики). Требуемые для этого графические изображения поставляются как часть Visual Studio; их можно найти в C:Program Files\Microsoft Visual Studio.NET\Common7\Graphics\bitmaps\assorted с именами файлов

    CLUB.BMP
    ,
    DIAMOND.BMP
    ,
    HEART.BMP
    и
    SPADE.BMP
    . Скопируйте их в каталог проекта, чтобы ими можно было воспользоваться. 

    Добавим некоторый код к новому элементу управления. В файл

    PCSUserC1.ascx
    , представленный в виде кода HTML, добавим следующие строки:

    <HTML>

     <HEAD> </HEAD>

     <BODY>

      <TABLE CellSpacing=4>

       <TR vAlign=middle>

        <TD>

         <asp:Image Runat="server" ID="suitPic" ImageURL="club.bmp " />

        </TD>

        <TD height=20>

         <asp:Label Runat="server" ID="suitLabel">Club</asp:Label>

        </TD>

       </TR>

      </TABLE>

     </BODY>

    </HTML>

    Этот код определяет состояние по умолчанию элемента управления, которое будет изображением трефы с меткой. Прежде чем добавлять дополнительную функциональность, протестируем такое поведение по умолчанию, добавляя этот элемент управления в проект Web-страницы

    WebForm1.aspx
    .

    Чтобы использовать специальный элемент управления в файле

    .aspx
    , сначала необходимо определить, как мы будем на него ссылаться, то есть, имя тега, который будет представлять элемент управления в коде HTML. Чтобы сделать это, используется директива
    <%@ Register %>
    в верхней части кода следующим образом:

    <%@ Register TagPrefix="PCS" TagName="UserC1" Src="PCSUserC1.ascx" %>

    Здесь используются атрибут

    Src
    для указания на файл, содержащий элемент управления пользователя, и атрибуты
    TagPrefix
    и
    TagName
    для определения имени тега для использования (в форме
    TagPrefix: TagName
    ). Теперь мы можем использовать этот элемент управления, добавляя следующий элемент:

    <%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false"

     Inherits="PCSUserCWebApp1.WebForm1" %>

    <%@ Register TagPrefix="PCS" TagName="UserC1" Src= "PCSUserC1.ascx" %>

    <HTML>

     <HEAD>

      <meta name=vs_targetSchema content="Internet Explorer 5.0">

      <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">

      <meta name="CODE_LANGUAGE" Content="C#" >

     </HEAD>

     <BODY MS_POSITIONING="GridLayout">

      <form method="post" runat="server">

       <PCS:UserC1 Runat="server" id="myUserControl" />

      </form>

     </BODY>

    </HTML>

    Элементы управления пользователя могут не объявляться по умолчанию в базовом коде формы, поэтому может понадобиться добавить следующее объявление в

    WebForm1.aspx.cs
    :

    public class WebForm1 : System.Web.UI.Page {

     protected PCSUserC1 myUserControl;

     ...

    Это все, что нужно сделать для тестирования элемента управления пользователя, и выполнение проекта приведет к следующему результату:

     club

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

    Чтобы получить управление над выводимой мастью, можно использовать атрибут элемента

    <PCS: UserC1>
    . Атрибуты элементов для элементов управления пользователя автоматически отображаются в свойства элементов управления пользователя, поэтому для того, чтобы это заработало, необходимо только добавить свойство в код элемента управления
    PCSUserC1.ascx.cs
    . Назовем это свойство
    Suit
    и позволим ему принимать значение любой масти. Чтобы упростить представление состояния элемента управления, определим внутри пространства имен
    PCSUserCWebAppl
    перечисление для хранения четырех названий мастей:

    namespace PCSUserCWebAppl {

     ...

     public enum suit {

      club, diamond, heart, spade

     }

     ...

    }

    Для класса

    PCSUserC1
    требуется переменная-член для хранения типа данных suit (масть) —
    currentSuit
    :

    public class PCSUserC1 : System.Web.UI.UserControl {

     protected System.Web.UI.WebControls.Image suitPic;

     protected System.Web.UI.WebControls.Label suitLabel;

     protected suit currentSuit;

    А также свойство для доступа к этой переменной-члену,

    Suit
    :

    public suit Suit {

     get {

      return currentSuit;

     }

     set {

      currentSuit = value;

      suitPic.ImageUrl = currentSuit.ToString() + ".bmp";

      suitLabel.Text = currentSuit.ToString();

     }

    }

    Здесь метод доступа

    set()
    задает URL изображения как один из файлов, скопированных ранее, а текст выводит название масти.

    Теперь элемент управления закончен, и нам надо добавить код в

    WebForm1.aspx
    для доступа к этому новому свойству. Используем список переключателей для выбора масти:

    <BODY MS_POSITIONING="GridLayout">

     <form method="post" runat="server">

      <PCS:UserC1 Runat="server" id="myUserControl" />

      <asp:RadioButtonList Runat="server" ID="suitList" autopostback="True">

       <asp:ListItem Value="club" Selected="True">Club</asp:ListItem>

       <asp:ListItem Value="diamond">Diamond</asp:ListItem>

       <asp:ListItem Value="heart">Heart</asp:ListItem>

       <asp:ListItem Value="spade">Spade</asp:ListItem>

      </asp:RadioButtonList>

     </form>

    </BODY>

    Нам нужно также добавить обработчик событий для события списка

    SelectedIndexChanged
    , который мы можем сделать просто с помощью двойного щелчка мышью на элементе управления в графическом представлении.

    Отметим, что нужно задать свойство autopostback этого списка как

    true
    , так как обработчик события
    suitList_SelectedIndexChanged()
    не будет выполняться на сервере, если обратная отправка не задана, и этот элемент управления не включает обратную отправку по умолчанию.

    Для метода

    suitList_SelectedIndexChanged()
    требуется следующий код в
    WebForm1.aspx.cs
    :

    protected void suitList_SelectedIndexChanged(object sender, System.EventArgs e) {

     MyUserControl.Suit = (suit)Enum.Parse(typeof(suit), suitList.SelectedItem.Value);

    }

    Мы знаем, что атрибуты

    value
    элементов
    <ListItem>
    представляют допустимые значения перечисления
    suit
    , которое было определено ранее, поэтому мы анализируем их просто как типы перечислений (у нас здесь то же пространство имен, поэтому нам не нужно переопределять тип) и используем их как значения свойства
    Suit
    элемента управления пользователя. Мы преобразуем возвращаемый тип
    object
    в
    suit
    с помощью простого синтаксиса преобразования типов, и это невозможно сделать неявно.

    Не нужно это усложнять, просто определим одно значение с помощью атрибута

    Suit
    формы Web, например:

    <PCS:UserC1 Runat="server" id="myUserControl" Suit="diamond" />

    Процессор ASP.NET достаточно разумен, чтобы получить правильный элемент перечисления из предоставленной строки:

    Теперь можно изменять масть при выполнении этого приложения Web:

    Затем мы зададим для элемента управления несколько методов. Это снова сделать несложно, нам нужно только добавить методы в класс

    PCSUserC1
    :

    public void Club() {

     Suit = suit.club;

    }


    public void Diamond() {

     Suit = suit.diamond;

    }


    public void Heart() {

     Suit = suit.heart;

    }


    public void Spade() {

     Suit = suit.spade;

    }

    Эти четыре метода —

    Club()
    ,
    Diamond()
    ,
    Heart()
    и
    Spade()
    — изменяют выведенную на экран масть на ту, которая была указана.

    Мы вызываем эти функции из четырех элементов управления на странице

    .aspx
    :

     <asp:ImaqeButton Runat="server" ID="clubButton"

      ImageUrl="CLUB.BMP" OnClick="clubButton_OnClick" />

     <asp:ImageButton Runat="server" ID="diamondButton"

      ImageUrl="DIAMOND.BMP" OnСlick="diamondButton_OnClick" />

     <asp:ImageButton Runat="server" ID="heartButton"

      ImageUrl="HEART.BMP"  OnClick="heartButton_OnClick" />

     <asp:ImageButton Runat="server" ID="spadeButton"

      ImageUrl="SPADE.BMP" OnClick="spadeButton_OnClick" />

    </form>

    С помощью следующих обработчиков событий:

    protected void clubButton_OnClick(object sender, System.Web.UI.ImageClickEventArgs e) {

     myUserControl.Club()

    }


    protected void diamondButton_OnClick(object sender, System.Web.UI.ImageClickEventArgs e) {

     myUserControl.Diamond();

    }


    protected void heartButton_OnClick(object sender, System.Web.UI.ImageClickEventArgs e) {

     myUserControl.Heart();

    }


    protected void spadeButton_OnClick(object sender, System.Web.UI.ImageClickEventArgs e) {

     myUserControl.Spade();

    }

    Теперь мы имеем четыре новые кнопки, которые можно использовать для изменения масти:

    Отметим, что эти кнопки не изменяют выбранный переключатель, хотя сделать это было бы достаточно просто.

    Теперь, создав элемент управления пользователя, можно использовать его на любой другой странице Web с помощью директивы

    <%@ Register %>
    и двух файлов исходного кода (
    PCSUserC1.ascx
    и
    PCSUserC1.ascx.cs
    ), созданных для элемента управления.

    Преобразование приложения предварительного заказа мероприятия в элемент управления пользователя

    В большинстве случаев преобразование страницы ASP.NET в элемент управления пользователя выполнить легко, так как можно просто скопировать требуемый код в пустые файлы

    .ascx
    и
    ascx.cs
    . Можно даже выйти из положения в некоторых случаях, просто изменяя имя файла на
    .ascx
    , если поместить весь код C# в этот файл, а не использовать режим "code behind".

    Однако в некоторых случаях это невозможно, так как существуют препятствия, которые необходимо рассмотреть, чтобы преобразовать приложение Web из предыдущей главы в элемент управления пользователя.

    Это приложение использует переменную уровня приложения для множества данных, которая содержит таблицы данных мероприятий, участников и названий помещений. Если желательно использовать эту переменную таким же образом, нам понадобиться в этом проекте поместить код для извлечения множества данных в файле global.asax. То есть нам еще нужно добавить в проект ссылку Web на требуемую службу Web.

    Существуют другие изменения, которые необходимо сделать, чтобы учесть факт, что элемент управления имеет базовый класс

    UserControl
    , а не
    Form
    . Например,
    UserControl
    не имеет коллекции
    Validators
    , поэтому невозможно просмотреть объекты
    Validator
    в этой коллекции с помощью кода, который использовался ранее:

    protected void submitButton_click(object sender, System.EventArgs e) {

     foreach (System.Web.UI.WebControls.WebControl validator in this.Validators) {

      validator.Enabled = true;

     }

     this.Validate();

     if (this.IsValid) {

      ...

    Вместо этого необходимо использовать следующий подход:

    protected void submitButton_Click(object sender, System.EventArgs e) {

     validateEvent.Enabled = true;

     validateRoom.Enabled = true;

     validateName.Enabled = true;

     validateAttendees.Enabled = true;

     validateEvent.Validate();

     validateRoom.Validate();

     validateName.Validate();

     validateAttendees.Validate();

     if (validateAttendees.IsValid && validateEvent.IsValid &&

         validateRoom.IsValid && validateName.IsValid) {

      ...

    Существует также проблема с тем. что элемент управления использует итоговую проверку. Значит, другие области на странице, использующей этот элемент управления, будут требовать дополнительной работы для проверки. Это связано с тем, что итоговая проверка работает для всей страницы, включая все элементы управления проверкой, несмотря на то, где они находятся, поэтому может возникнуть путаница. Возможно, простейшим решением здесь является создание собственного обработчика проверки, который использует элементы управления проверкой только в элементе управления пользователя, один из них — элемент управления меткой, с помощью которого выводится требуемый текст вручную. Но здесь этот подход рассматриваться не будет.

    В целом необходимо сделать все элементы управления пользователя полностью независимыми, что по сути означает, что данное приложение не является идеальным. Однако оно высветило некоторые ограничения элементов управления пользователя.

    В следующем разделе мы увидим, как можно изменять элементы управления в значительно большей степени с помощью синтаксиса специальных элементов управления.

    Специальные элементы управления

    Специальные элементы управления — следующий шаг по сравнению с элементами управления пользователя в том смысле, что они являются полностью самодостаточными в сборках C#, не требуя отдельного кода ASP.NET. Это означает, что нам не нужно проходить через процесс сборки UI в файле

    .ascx
    . Вместо этого мы имеем полный контроль над тем, что записывается в поток вывода, то есть, над кодом HTML, созданным элементом управления.

    Обычно для разработки специального элемента управления требуется больше времени, чем для элемента управления пользователя, так как синтаксис является более сложным и часто требуется написать значительно больший код, чтобы получить результат. Элемент управления пользователя, как мы видели, может просто объединять несколько других элементов управления, в то время как специальный элемент управления может делать практически что угодно.

    Чтобы получить наиболее гибкое поведение специальных элементов управления, можно выводить класс из

    System.Web.UI.WebControls.WebControl
    . В этом случае создается полный специальный элемент управления. Иначе можно расширить функциональность существующего элемента управления, создавая производный специальный элемент управления. Наконец, можно сгруппировать существующие элементы управления почти так, как это делалось в предыдущем разделе, но с более логичной структурой, чтобы создать композитный специальный элемент управления:

    Любой из этих элементов может использоваться на страницах ASP.NET одинаково. Необходимо только поместить созданную сборку в каталог

    bin
    приложения Web, которое будет его использовать, и зарегистрировать имена используемых элементов с помощью директивы
    <%@ Register %>
    . В этой директиве применяется немного другой синтаксис для специальных элементов управления:

    <%@ Register TagPrefix="PCS" Namespace="PCSCustomWebControls" Assembly="PCSCustomWebControls" %>

    Мы используем параметр

    TagPrefix
    таким же образом, как и раньше, но не используем атрибуты
    TagName
    или
    Src
    . Это связано с тем, что сборка специального элемента управления может содержать несколько специальных элементов управления, и каждый из них будет именован согласно своему классу, поэтому
    TagName
    является лишним. Кроме того, так как сборка находится в каталоге
    bin
    , мы можем использовать средства платформы .NET для динамического обнаружения требуемой сборки просто по имени и пространству имен в ней, которое содержит элементы управления.

    Выше, в примере строки кода, мы говорим, что хотим использовать сборку с именем

    PCSCustomWebControls.dll
    с элементами управления в пространстве имен
    PCSCustomWebControls
    , и при этом используем префикс
    PCS
    . Если в этом пространстве имен имеется элемент управления с именем
    Control1
    , то можно использовать его с кодом ASP.NET:

    <PCS:Control1 Runat="server" ID="MyControl" />

    С помощью специальных элементов управления можно также воспроизвести некоторое вложенное поведение элементов управления, такое, как мы видим в списке элементов управления:

    <asp:dropdownlist id="roomList" runat="server" width="160px">

     <asp:ListItem Value="1">The Happy Room</asp:ListItem>

     <asp:ListItem Value="2">The Angry Room</asp:ListItem>

     <asp:ListItem Value="3">The Depressing Room</asp:ListItem>

     <asp:ListItem Value="4">The Funked Out Room</asp:ListItem>

    </asp:dropdownlist>

    Можно создать очень похожим образом элементы управления, которые интерпретируются как потомки других элементов управления. Позже в этой главе мы увидим, как это делается.

    Конфигурация проекта специального элемента управления

    Применим часть этой теории на практике. Мы будем использовать для простоты единственную сборку для хранения всех специальных элементов управления примера этой главы, которую можно создать в Visual Studio.NET, выбирая новый проект типа Web Control Library. Назовем нашу библиотеку

    PCSCustomWebControls
    :

    Здесь проект создан в каталоге

    wwwroot
    , хотя это и не обязательно. Библиотеки элементов управления Web можно создавать где угодно, необходимо только скопировать созданную сборку в каталог
    bin
    приложения Web, которое ее использует.

    Один из технических приемов, применяемых для упрощения тестирования одиночного решения, состоит в добавлении проекта приложения Web к тому же решению:

    В данный момент это единственное приложение, которое будет использовать нашу библиотеку специального элемента управления, поэтому для ускорения работы определим, что выводимая сборка библиотеки создается в правильном каталоге

    bin
    (то есть нам не нужно копировать файл после каждой перекомпиляции). Это можно сделать с помощью страниц свойств проекта
    PCSCustomWebControls
    :

    Отметим, что здесь в раскрывающемся списке Configuration выбран элемент All Configurations, поэтому отладочная и окончательная сборка будут помещены в одном месте. Output Path был изменен на C:\Inetpub\wwwroot\PCSCustomWebControlsTestApp\bin Чтобы облегчить отладку можно также изменить значение Start URL на странице свойств Debugging на http://localhost/PCSCustomWebControlsTestApp/WebForm1.aspx, a Debug Mode - на URL таким образом, чтобы увидеть результаты, проект можно выполнять просто в режиме отладки.

    Убедимся, что все это работает, протестировав элемент управления, который поставляется по умолчанию в файле

    .cs
    для библиотеки специального элемента управления, называемой
    WebCustomControl1
    . Нам нужно внести следующие изменения в код
    WebForm1.aspx
    , который просто ссылается на вновь созданную библиотеку элемента управления и встраивает используемый по умолчанию элемент из этой библиотеки в тело страницы:

    <%@ Page language="c#" Codebehind="WebForm1.aspx.cs"

     AutoEventWireup="false" Inherits="PCSCustomWebControlsTestApp.WebForm1" %> 

    <%@ Register TagPrefix="PCS" Namespace="PCSCustomWebControls"

     Assembly="PCSCustomWebControls" %>

    <html>

     <head>

      <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">

      <meta name="CODE_LANGUAGE" Content = "C#">

      <meta name=vs_defaultClientScript content="JScript">

      <meta name=vs_targetSchema content="Internet Explorer 5.0">

     </head>

     <body MS_POSITIONING="GridLayout">

      <form id="WebForm1" method="post" runat="server">

       <PCS:WebCustomControl1 Runat="server" Text="Testing again..." />

      </form>

     </body>

    </html>

    Теперь, пока библиотека

    PCSCustomWebControls
    сконфигурирована как приложение запуска, можно нажать кнопку Debug, чтобы увидеть результаты работы:

    Добавим также ссылку на проект

    PCSCustomWebControls
    в раздел тестирования приложений:

    Затем добавим инструкцию

    using
    в пространство имен
    PCSCustomWebControlsTestApp
    в
    WebForm1.aspx.cs
    :

    using PCSCustomWebControls;
     

    Это позволит нам использовать наши специальные элементы управления из кода, скрытого за формой.

    Базовые специальные элементы управления

    Как можно предположить из результатов предыдущего раздела, образец элемента управления, создаваемый по умолчанию, является версией стандартного элемента управления

    <asp:Labels>
    . Создаваемый в файле
    .cs
    код проекта,
    WebCustomControl1.cs
    , выглядит следующим образом:

    namespace PCSCustomWebControls {

     using System;

     using System.Web.UI;

     using System.Web.UI.WebControls;

     using System.ComponentModel;


     /// <summary>

     /// Краткое описание WebCustomControl1

     /// </summary>

     [DefaultProperty("Text"),

     ToolboxData("<{0}WebCustomControl1 runat=server></{0}:WebCustomControl1>")]

     public class WebCustomControl1 : System.Web.UI.WebControls.WebControl {

      private string text;

      [Bindable(true), Category("Appearance"), DefaultValue(" ")]

      public string Text {

       get {

        return text;

       }

       set {

        text = value;

       }

      }


      /// <summary>

      /// Предоставить этот элемент управления указанному параметру вывода.

      /// </summary>

      /// <param name="output"> The HTML writer to write out to </param>

      protected override void Render(HtmlTextWriter output) {

       output.Write(Text);

      }

     }

    }

    Начальные инструкции

    using
    для пространств имен вполне стандартны.

    Здесь определен единственный класс

    WebCustomControl1
    (отметим, как имя класса отображается прямо в элемент ASP.NET в простом примере, только что увиденном), который является производным из класса
    WebControl
    , как обсуждалось ранее. Для этого класса предоставлены два атрибута:
    DefaultProperty
    и
    ToolboxData
    . Атрибут
    DefaultProperty
    определяет, какое свойство будет использоваться по умолчанию для элемента управления в языках, которые поддерживают эту функциональность. Атрибут
    ToolboxData
    точно определяет, какой код HTML будет добавлен к странице
    .aspx
    , если этот элемент управления добавляется с помощью инструментальной панели Visual Studio (когда проект откомпилирован, можно добавить элемент управления в панель инструментов, конфигурируя панель инструментов для использования созданной сборки).

    Класс содержит одно свойство:

    Text
    . Это очень простое текстовое свойство, похожее на те, которые встречались раньше. Здесь необходимо отметить только три атрибута:

    Bindable
    — показывает, может ли свойство быть связано с данными.

    Category
    — задает, будет ли свойство выводиться на страницах свойств.

    DefaultValue
    — значение по умолчанию для свойства.

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

    Остальная часть класса состоит из метода

    Render()
    . Это единственный самый важный метод для реализации при создании специальных элементов управления, так как в нем мы получаем доступ к потоку вывода для изображения содержимого элемента управления. Существует только два случая, когда этот метод не нужно реализовывать:

    □ Когда создается элемент управления, не имеющий визуального представления (обычно называемый компонентом).

    □ Когда создается производный элемент из существующего элемента управления и не требуется изменять характеристики его изображения.

    Специальные элементы управления могут также предоставлять специальные методы, инициировать специальные события, и отвечать производным элементам управления (если они существуют). Ниже мы рассмотрим:

    □ Создание производных элементов управления

    □ Создание композитных элементов управления

    □ Создание более развитых элементов управления

    Конечный пример будет элементом управления выбором, который разрешает пользователю голосовать за одну из нескольких возможностей, и выводить графически результаты голосования. Варианты выбора будут определяться с помощью вложенных элементов управления потомков способом, описанным ранее.

    Создадим для начала простой производный элемент управления.

    Производный элемент управления RainbowLabel

    Для этого первого примера создадим производный элемент управления из элемента управления

    Label
    и переопределим его метод
    Render()
    для вывода многоцветного текста. Чтобы держать элементы управления примера в этой главе отдельно, создадим новые файлы исходного кода, поэтому для этого элемента управления создадим новый файл
    .cs
    с именем
    RainbowLabel.cs
    и введем в него следующий код:

    namespace PCSCustomWebControls {

     using System;

     using System.Web.UI;

     using System.Web.UI.WebControls;

     using System.ComponentModel;

     using System.Drawing;


     public class RainbowLabel : System.Web.UI.WebControls.Label {

      private Color[] colors = new Color[] {

       Color.Red, Color.Orange, Color.Yellow,

       Color.GreenYellow, Color.Blue, Color.Indigo, Color.Violet

      };


      protected override void Render(HtmlTextWriter output) {

       string text=Text;

       for (int pos=0; pos < text.Length; pos++) {

        int rgb = colors[pos % 7].ToArgb() & 0xFFFFFF;

        output.Write("<font color="#" + rgb.ToString("X6") + "'>" + text[pos] + "</font>");

       }

      }

     }

    }

    Этот класс выводится из существующего элемента управления

    Label
    (
    System.Web.UI.WebControls.Label
    ) и не требует никаких дополнительных свойств, так как достаточно унаследованного свойства
    Text
    . Мы добавили новое скрытое поле —
    colors[]
    , которое содержит массив цветов, циклически изменяющихся при выводе текста.

    Основная функциональность элемента управления находится в

    Render()
    , который переопределен, так как мы хотим изменить вывод HTML. Здесь мы берем строку для вывода из свойства
    Text
    и выводим каждый символ цветом из массива
    colors[]
    .

    Чтобы протестировать этот элемент управления, необходимо добавить его к форме в

    PCSCustomWebControlsTestApp
    :

    <form method="post" runat="server" ID="Form1">

     <PCS:RainbowLabel Runat="server" Text="Multicolored label!"

      ID="rainbowLabel1" />

    </form>

    Нам нужно также добавить подходящее объявление в код, реализующий форму (если оно не добавится автоматически):

    public class WebForm1 : System.Web.UI.Page {

     protected RainbowLabel rainbowLabel1;

     ...

    В результате будет получено:

    Поддержание состояния в специальном элементе управления

    Каждый раз при создании элемента управления на сервере в ответ на запрос к серверу, он создается с самого начала. Это означает что любое простое поле элемента управления будет повторно инициализироваться. Чтобы элементы управления поддерживали состояние между запросами, они должны использовать

    ViewState
    , о чем требуется помнить при создании элементов управления.

    Чтобы проиллюстрировать это, добавим дополнительное свойство в элемент управления

    RainbowLabel
    . Мы добавив метод с именем
    Cycle()
    , который циклически перебирает доступные цвета и использует хранимое поле
    offset
    для определения цвета первой буквы выводимой строки.

    Это поле должно использовать

    ViewState
    элемента управления, чтобы быть устойчивым между запросами. Если это не сделано и поле инициализируется в элементе управления, то все будет работать неправильно.

    Здесь будет показан код для обоих случаев, чтобы увидеть ловушку, в которую очень легко попасть. Сначала мы посмотрим на код, который не может воспользоваться

    ViewState
    :

    public class RainbowLabel : System.Web.UI.WebControls.Label {

     private Color[] colors = new Color[] {

      Color.Red, Color.Orange, Color.Yellow,

      Color.GreenYellow, Color.Blue, Color.Indigo, Color.Violet

     };


     private int offset = 0;


     protected override void Render(HtmlTextWriter writer) {

      string text = Text;

      for (int pos = 0; pos < text.Length; pos++ ) {

       int rgb = colors[(pos + offset) % 7].ToArgb() & 0xFFFFFF;

       output.Write("<font color= '#" + rgb.ToString("X6") + "'>" + text[pos] + "</font>");

      }

     }


     public void Cycle() {

      offset = ++offset % 7;

     }

    }

    Здесь мы инициализируем поле

    offset
    нулем, а затем позволяем методу
    Cycle()
    увеличивать его. Использование оператора
    %
    гарантируем, что оно уменьшится до 0, если достигнет 7.

    Чтобы протестировать это, требуется способ вызова метода

    Cycle()
    и добавление кнопки к форме:

    <form method="post" runat="server" ID="Form1">

     <PCS:RainbowLabel Runat="server" Text="Multicolored label!"

      ID="rainbowLabel1" />

     <asp:Button Runat="server" ID="cycleButton"

      Text="Cycle colors" OnClick="cycleButton_Click" />

    </form>

    Co следующим обработчиком событий:

    protected void cycleButton_Click(object sender, System.EventArgs e) {

     this.rainbowLabel1.Cycle();

    }

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

    Если этот элемент управления сохраняет себя на сервере между запросами, то он работает правильно, так как поле

    offset
    поддерживает свое состояние, не требуя специального внимания. Однако эта техника не имеет смысла для приложения Web, когда потенциально тысячи пользователей используют его одновременно. Создание отдельного экземпляра для каждого пользователя только ухудшает ситуацию.

    В любом случае решение достаточно просто. Мы должны использовать свойство

    ViewState
    элемента управления для сохранения и извлечения данных. Нам не нужно беспокоиться о том, как это сериализуется, создается заново или о чем-то еще, мы просто помещаем данные в свойство и извлекаем данные, уверенные, что состояние будет поддерживаться между запросами стандартными средствами ASP.NET.

    Чтобы поместить поле

    offset
    в
    ViewState
    , мы используем следующий код:

    ViewState["_offset"] = offset;

    ViewState
    состоит из пар имя-значение, и в данном случае используется имя
    _offset
    . Нам не нужно объявлять его где-либо, оно будет создано при первом использовании этого кода.

    Аналогично для извлечения состояния используется код:

    offset = (inc)ViewState["_offset"];

    Если мы сделаем это, когда ничего не хранится в

    ViewState
    под этим именем, то будет получено значение
    null
    . Простейший способ справиться с такой ситуацией — использовать вызов в блоке
    try
    .

    Собирая все вместе, сделаем следующие изменения в коде:

    public class RainbowLabel : System.Web.UI.WebControls.Label {

     private Color[] colors = new Color[] {

      Color.Red, Color.Orange, Color.Yellow,

      Color.GreenYellow, Color.Blue, Color.Indigo, Color.Violet

     };


     private int Offset;


     protected override void Render(HtmlTextWriter writer) {

      string text = Text;

      GetOffset();

      for (int pos = 0; pos < text.Length; pos++) {

       int rgb = colors[(post + offset) % 7].ToArgb() & 0xFFFFFF;

       writer.Write("<font color='#" + rgb.ToString("X6") + "'>" + text[pos] + "</font>");

      }

     }


     private void GetOffset() {

      try {

       offset = (int)ViewState["_offset"],

      } catch {

       offset = 0;

      }

     }


     public void Cycle() {

      GetOffset();

      offset = ++offset % 7;

      ViewState["_offset"] = offset;

     }

    }

    Теперь элемент управления позволит методу

    Cycle()
    работать каждый раз. Обычно
    ViewState
    используется для простых свойств, таких как свойства
    string
    :

    public string Name {

     get {

      return (string)ViewState["_name"];

     }

     set {

      ViewState["_name"] = value;

     }

    }

    Еще одно замечание об использовании

    ViewState
    имеет отношение к элементам управления-потомкам. Если элемент управления имеет потомков и используется на странице больше одного раза, то возникает проблема, связанная с тем, что потомки будут по умолчанию сообща использовать свой
    ViewState
    . Практически в каждом случае это не то поведение, которое требуется, и к счастью мы имеем простое решение. Выводя элемент управления-предок из
    INamingContainer
    мы вынуждаем элементы управления-потомки использовать квалифицированное хранилище в
    ViewState
    , так что элементы управления-потомки не будут сообща использовать свой
    ViewState
    с аналогичными элементами управления-потомками с другим предком.

    Использование этого интерфейса не требует никакой дополнительной реализации, нам нужно просто сказать, что мы его используем, как если бы это был просто маркер для интерпретации сервером ASP.NET. Мы сделаем это в следующем разделе.

    Создание композитного специального элемента управления

    В качестве несложного примера композитного специального элемента управления можно объединить элемент управления из предыдущего раздела с кнопкой цикла, которая имелась на тестовой форме.

    Назовем этот композитный элемент управления

    RainbowControl2
    и поместим его в новый файл
    RainbowControl2.cs
    . Этот элемент управления должен делать следующее:

    □ Наследовать из

    WebControl
    (а не от
    Label
    в этот раз)

    □ Поддерживать

    INamingContainer

    □ Иметь два поля для хранения своих элементов управления-потомков

    public class RainbowLabel2 : System.Web.UI.WebControls.WebControl, INamingContainer {

     private RainbowLabel rainbowLabel = new RainbowLabel();

     private Button cycleButton = new Button();

    Чтобы сконфигурировать композитный элемент управления, необходимо сделать так, чтобы всякий элемент управления-потомок добавлялся к коллекции

    Controls
    и правильно инициализировался. Мы делаем это, переопределяя метод
    CreateChildControls()
    и помещая туда необходимый код:

    protected override void CreateChildControls() {

     cycleButton.Text = "Cycle colors.";

     cycleButton.Click += new System.EventHandler(cycleButton_Click);

     Controls.Add(cycleButton);

     Controls.Add(rainbowLabel);

    }

    Здесь мы используем только метод

    Add()
    коллекции
    Controls
    , чтобы работа была корректной. Добавляем также обработчик событий для кнопки, чтобы мы могли циклически менять цвета, это достигается точно таким же образом, как и для других событий. Обработчик событий уже знаком:

    protected void cycleButton_Click(object sender, System.EventArgs e) {

     rainbowLabel.cycle();

    }

    Данный вызов заставляет цвета метки циклически изменяться.

    Чтобы предоставить пользователям композитного элемента управления доступ к тексту в потомке

    rainbowLabel
    , можно добавить свойство, которое отображается в свойство
    Text
    потомка:

    public string Text {

     get {

      return rainbowLabel.Text;

     }

     set {

      rainbowLabel.Text = value;

     }

    }

    Осталось только реализовать

    Render()
    . По умолчанию, если не переопределить этот метод, вызывается метод
    Render()
    каждого элемента управления потомка. Однако, чтобы получить дополнительный контроль над этим, можно самим вызывать эти методы или, лучше, открытые экземпляры методов
    RenderControl()
    :

    protected override void Render(HtmlTextWriter writer) {

     rainbowLabel.RenderControl(writer);

     cycleButton.RenderControl(writer);

    }

    Здесь не выводится никакой код HTML, хотя можно было легко это сделать. Нам нужно просто передать полученный экземпляр

    HtmlTextWriter
    в метод
    RenderControl()
    для потомка, чтобы обычным образом был вставлен HTML, созданный этим потомком. Можно использовать такой элемент управления почти так же, как и
    RainbowLabel
    :

    <form method="post" runat="server" ID="Form1">

     <PCS:RainbowLabel2 Runat="server"

      Text="Multicolored label composite" ID="rainbowLabel2" />

    </form>

    вместе со связанным объявлением в скрытом коде формы.

    Элемент управления выборочным опросом

    Теперь воспользуемся изложенной техникой и создадим более сложный специальный элемент управления. Конечный результат позволит с помощью следующего кода ASP.NET:

    <form method="post" runat="server" ID="Form1">

     <PCS: StrawPoll Runat="server" ID="strawPoll1"

      PollStyle="voteonly" Title="Who is your favorite James Bond?">

      <PCS:Option Name="SeanConnery" Votes="101" />

      <PCS:Option Name="Roger Moore" Votes="83" />

      <PCS:Option Name="George Lazenby" Votes="32" />

      <PCS:Option Name="Timothy Dalton" Votes="28" />

      <PCS:Option Name="Pierce Brosnan" Votes="95" />

     </PCS:StrawPoll>

    </form>

    получить:

    И когда мы нажмем на кнопку vote, изображение изменится на следующее:

    Альтернативно, в основном для целей тестирования, можно выводить результаты и кнопки голосования одновременно, и разрешить подавать несколько голосов.

    Код ASP.NET вовлечен явно в задание свойства

    Name
    и
    Votes
    для каждого варианта
    Option
    . Это прекрасно подходит для данного примера, хотя можно предвидеть, что более развитая версия этого элемента управления соединится с данными для получения результатов. Однако здесь это рассматриваться не будет, так как может оказаться достаточно сложным.

    При анализе кода ASP.NET такие структуры интерпретируются согласованным образом: каждый управляющий элемент-потомок интерпретируется способом, который определен в классе-построителе элемента управления, связанным с элементом управления предком. Этот построитель элемента управления, код которого мы скоро увидим, обрабатывает все вложенное внутрь элемента управления, с которым он связан, включая литеральный текст.

    Нам нужно создать два элемента управления:

    Option
    — для хранения отдельных вариантов выбора и
    StrawPoll
    , который будет содержать и выводить элемент управления выборочного опроса. Оба эти элемента управления будут помещены в новый файл исходного кода:
    StrawPoll.cs
    .

    Элемент управления Option

    Для начала создадим элементы управления Option, каждый из которых будет содержать имя варианта выбора и количество голосов, поданных за этот вариант выбора. Кроме того, эти элементы управления будут поддерживать кнопки голосования и обрабатывать все сделанные нажатия этих кнопок.

    Поэтому нам потребуется:

    □ Код для свойств

    Name
    и
    Votes
    (хранимых в
    ViewState
    )

    □ Код инициализации в

    CreateChildControls()

    □ Код для обработчика нажатия кнопки

    Мы включаем также вспомогательный метод

    Increment()
    , который будет добавлять голос к текущему счету. Этот вспомогательный метод вызывается обработчиком нажатия кнопки.

    Нам понадобится также поддержка

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

    Код класса

    Option
    будет находиться в файле
    StrawPoll.cs
    , который мы должны добавить к проекту вместе со стандартными инструкциями namespace и
    using
    , согласно уже известным нам элементам управления
    RainbowLabel
    . Код будет иметь следующий вид:

    public class Option : System.Web.UI.WebControls.WebControl, INamingContainer {

     public string Name {

      get {

       return (string)ViewState["_name"];

      }

      set {

       ViewState["_name"] = value;

      }

     }


     public long Votes {

      get {

       return (long)ViewState["_votes"];

      }

      set {

       ViewState["_votes"] = value;

      }

     }


     public void Increment() {

      ViewState["_votes"] =(long)ViewState["_votes"] + 1;

     }


     public void Reset() {

      ViewState["_votes"] = 0;

     }


     protected override void CreateChildControls() {

      Button btnVote = new Button();

      btnVote.Text = "Vote";

      btnVote.Click += new System.EventHandler(btnVote_Click);

      Controls.Add(btnVote);

     }


     protected void btnVote_Click(object sender, System.EventArgs e) {

      Increment();

     }

    }

    Отметим, что метод

    Render()
    не был здесь переопределен. Это связано с тем, что этот элемент управления имеет одного наследника, кнопку голосования и никакой другой информации для вывода. Поэтому можно использовать значение по умолчанию, которое будет просто изображением кнопки.

    Построитель элемента управления StrawPoll

    Теперь мы рассмотрим, как можно транслировать код ASP.NET каждого варианта выбора в элемент управления, который является потомком элемента управления

    StrawPoll
    . Чтобы сделать это, необходимо ассоциировать построитель элемента управления с классом
    StrawPoll
    с помощью атрибута
    ControlBuilderAttribute
    . Нам нужно также определить, что элементы управления-потомки не должны анализироваться никаким другим способом с помощью атрибута
    ParseChildren
    :

    [ControlBuilderAttribute(typeof(StrawPollControlBuilder)) ]

    [ ParseChildren(false) ]

    public class StrawPoll : System.Web.UI.WebControls.WebControl, INamingContainer { }

    Здесь используется класс с именем

    StrawPollControlBuilder
    , определенный следующим образом:

    internal class StrawPollControlBuilder : ControlBuilder {

     public override Type GetChildControlType(string tagName, IDictionary attribs) {

      if (tagName.ToLower().EndsWith("option")) return typeof(Option);

      return null;

     }


     public override void AppendLiteralString(string s) {

      // ничего не делать, чтобы избежать добавления встроенного текста

      // к элементу управления

     }

    }

    Здесь мы переопределяем метод

    GetChildControlType()
    базового класса
    ControlBuilder
    чтобы он возвращал тип класса
    Option
    в ответ на тег с именем
    <Option>
    . Фактически, чтобы все работало в максимальном количестве ситуаций, мы ищем любое имя тега, которое оканчивается строкой
    "option"
    с буквами в верхнем или нижнем регистре.

    Мы переопределяем также метод

    AppendLiteralString()
    так, чтобы любой промежуточный текст, включая пробелы, игнорировался и не вызывал никаких проблем.

    Когда это сделано в предположении, что в

    StrawPoll
    нет никаких других элементов управления, мы будем иметь все элементы управления
    Option
    содержащимися в коллекции
    Controls
    из
    StrawPoll
    . Эта коллекция не будет содержать никаких других элементов управления.

    Отметим, что построитель элементов управления использует коллекцию атрибутов. Чтобы использовать это добавим следующую инструкцию

    using
    в пространство имен:

    using System.Collections;

    Стиль StrawPoll

    Прежде чем перейти к рассмотрению самого класса

    StrawPoll
    , необходимо рассмотреть еще один вопрос проектирования.
    StrawPoll
    должен выводиться в трех формах:

    □ Только кнопки для голосования

    □ Только результаты

    □ Кнопки для голосования и результаты

    Для этого можно определить перечисление, которое затем использовать как свойство элемента управления

    StrawPoll
    :

    public enum pollStyle {

     voteonly, valuesonly, voteandvalues

    }

    Как мы видели ранее, свойства, которые являются перечислениями, легко использовать, и мы можем применять текстовые имена в качестве значений атрибутов в ASP.NET.

    Элемент управления StrawPoll

    Теперь соберем все вместе. Для начала определим два свойства:

    Title
    дли вывода заголовка в элементе управления и
    PollStyle
    для хранения перечисления типа вывода. Оба они будут использовать
    ViewState
    для сохранения состояния:

    [ ControlBuilderAttribute (typeof (StrawPollControlBuilder)) ]

    [ ParseChildren(false) ]

    public class StrawPoll : System.Web.UI.WebControls.WebContol, INamingContainer {

     private string title = "Straw Poll";

     private pollStyle currentPollStyle = pollStyle.voteandvalues;


     public string Title {

      get {

       return title;

      }

      set {

       title = value;

      }

     }


     public pollStyle PollStyle {

      get {

       return currentPollStyle;

      }

      set {

       currentPollStyle = value;

      }

     }

    }

    Остальная часть этого класса посвящена методу

    Render()
    . Он будет выводить весь элемент управления выборочного опроса вместе со всеми вариантами выбора, принимая в расчет используемый стиль опроса. Мы выводим кнопки голосования, вызывая метод
    RenderControl()
    производных элементов управления
    Option
    , и выводим результаты опроса графически и численно с помощью свойств Votes производных элементов управления
    Option
    для создания простого кода HTML.

    Код, прокомментированный для ясности, будет выглядеть следующим образом:

    protected override void Render(HtmlTextWriter writer) {

     Option CurrentOption;

     long iTotalVotes = 0;

     long iPercentage = 0;

     int iColumns = 2;

     // Начало таблицы, изображение таблицы

     if (currentPollStyle == pollStyle.voteandvalues) {

      iColumns = 3;

     }

     writer.Write("<TABLE border='1' bordercolor='black' bgcolor='#DDDDEB'" +

      " width= '90%' cellpadding='1' cellspacing='1'" + " align='center'>");

     writer.Write("<TR><TD colspan='" + iColumns + align='center'"

      + " bgcolor='#FFFFDD'>");

     writer.Write("<B>" + title + "</B></TD></TR>");

     if (Controls.Count == 0) {

      // текст по умолчанию, когда нет вариантов выбора

      writer.Write("<TR><TD bgcoLor='#FFFFDD'>No options to" + " display.</TR></TD>");

     } else {

      // Получить общее число голосов

      for (int iLoop = 0; iLoop < Controls.Count; iLoop++) {

       // Получить вариант выбора

       currentOption = (Option)Controls[iLoop];

       // Просуммировать результаты голосования

       iTotalVotes += currentOption.Votes;

      }

      // Вывести каждый вариант выбора

      for (int iLoop = 0; iLoop < Controls.Count; iLoop++) {

       // Получить вариант выбора

       currentOption = (Option)Controls[iLoop];

       // Поместить имя варианта выбора в первый столбец

       writer.Write("<TR><TD bgcolor='#FFFFDD' width="15%'> " +

        currentOption.Name + " </TD>");

       // Добавить вариант голосования во второй столбец,

       // если требуется

       if (currentPollStyle != pollStyle.valuesonly) {

        writer.Write("<TD width='1%' bgcolor='#FFFFDD'>"

         + "<FONT Color='#FFFVDD'>.</FONT>");

        currentOption.RenderControl(writer);

        writer.Write("<FONT Color = '#FFFFDD'>.</FONT></TD>");

       }

       // Поместить график, значение и проценты в третьем столбце,

       // если требуется

       if (currentPollStyle != pollStyle.voteonly) {

        if (iTotalVotes > 0) {

         iPercentage = (currentOption.Votes * 100) / iTotalVotes;

        } else {

         iPercentage = 0;

        }

        writer.Write("<ТD bgcolor='#FFFFDD'><TABLE width='100%'>"

         + "<TR><TD><TABLE border='1' bordercolor= 'black' "

         + " width= '100%' cellpadding='0' " + " cellspacing='0'>");

        writer.Write("<TR><TD bgcolor='red' width='" + iPercentage

         + "%'><FONT соlor='red'>.</FONT></TD>");

        writer.Write<"TD bgcolor='white' width='" + (100-iPercentage) +

         "%'><FONT color='white'>." +

         "</FONT></TD></TR></TABLE></TD>");

        writer.Write("<TD width='75'>" + сurrentOption.Votes +

         " (" + iPercentage + "%)</TD><TR></TABLE></TD>");

       }

       // Конец строки

       writer.Write("</TR>");

      }

      // показать общее тело голосов, если выводятся значения

      if (currentPollStyle != pollStyle.voteonly) {

       writer.Write("<TR><TD bgcolor='#FFFFDD' colspan='" +

        iColumns + "'>Total votes cast: " + iTotalVotes + "</TD></TR>");

      }

     }

     // Завершить таблицу

     writer.Write("</TABLE>");

    }

    Если выборочный опрос выводится в режиме

    voteonly
    , то голосование должно инициировать изменение изображения в режиме
    valuesonly
    .
    Чтобы сделать это, нам потребуется небольшое изменение в обработчике кнопки голосования в классе
    Option
    :

    protected void btnVote_Click(object sender, System.EventArgs e) {

     Increment();

     StrawPoll parent = (StrawPoll)Parent;

     if (parent.PollStyle == pollStyle.voteonly) {

      parent.PollStyle = pollStyle.valuesonly;

     }

    }

    Теперь все готово к проведению голосования.

    Добавление обработчика событий

    Часто при использовании специальных элементов управления желательно инициировать специальные события и предоставить пользователям элемента управления действовать в соответствии с этим. В случае элемента управления выборочного опроса было бы хорошо иметь событие

    Voted
    , которое будет уведомлять форму, что голосование выполнено, и предоставлять ей всю информацию, необходимую для действия в этом случае.

    Чтобы зарегистрировать специальное событие, надо добавить в элемент управления код, аналогичный представленному ниже:

    public event EventHandler Voted;


    protected void OnVoted(EventArgs e) {

     Voted(this, e);

    }

    Тогда, как только нам понадобиться инициировать событие, мы просто вызываем метод

    OnVoted()
    , передавая аргументы события.

    Когда вызывается

    OnVoted()
    , инициируется событие, в соответствии с которым может действовать пользователь. Чтобы сделать это, пользователю необходимо зарегистрировать обработчик событий для этого события:

    strawPoll1.Voted += new EventHandler(this.StrawPoll1_OnVoted);

    Пользователь должен также предоставить код обработчика

    strawPoll1_OnVoted()
    . Мы слегка расширим этот метод, добавляя специальные аргументы для события, чтобы сделать доступным элемент управления Option, который инициирует событие. Назовем наш объект специального аргумента
    OptionEventArgs
    и определим его в
    StrawPoll.cs
    следующим образом:

    public class OptionEventArgs : EventArgs {

     public Option originatingOption;

    }

    Добавляем дополнительное открытое поле в существующий класс

    EventArgs
    . Так как мы изменили используемые аргументы, нам потребуется также специализированная версия представителя
    EventHandler
    , которая может объявляться в пространстве имен
    PCSCustomWebControls
    следующим образом:

    public delegate void Option EventHandler(object sender, OptionEventArgs e);

    Можно использовать эти примеры в

    StrawPoll
    следующим образом:

    public class StrawPoll : System.Web.UI.WebControls.WebControl, INamingContainer {

     private string title = "Straw Poll";

     private pollStyle currentPollStyle = pollStyle.voteandvalues;

     public event OptionEventHandler Voted;


     protected void OnVoted(OptionEventArgs e) {

      Voted(this, e);

     }

    Также мы имеем метод для инициирования события, вызываемый из элементов управления потомков

    Option
    при нажатии кнопки голосования:

     public void ChildVote(OptionEventArgs e) {

      OnVoted(e);

     }

    Наконец, нужно сделать дальнейшую модификацию обработчика события кнопки голосования в

    Option
    , чтобы вызывать этот метод, задавая для него правильные параметры:

    protected void btnVote_Click(object sender, System.EventArgs e) {

     Increment();

     StrawPoll parent = (StrawPoll)Parent;

     if (parent.PollStyle == pollStyle.voteonly) {

      parent.PollStyle = pollStyle.valuesonly;

     }

     OptionEventArgs eOption = new OptionEventArgs();

     eOption.originatingOption = this;

     parent.ChildVote(eOption);

    }

    Теперь можно реализовать обработчик событий на странице с помощью элемента управления. Мы должны просто определить его на странице ASP.NET, добавляя метку для использования в обработчике.

    <form id=Form1 method= post runat="server">

     <PCS:StrawPoll id=strawPoll1

      title="Who is your favorite James Bond?"

      Runat="server" OnVoted="strawPoll1_OnVoted" PollStyle="voteonly">

      <PCS:Option Name="Sean Connery" Votes="101" />

      <PCS:Option Name="Roger Moore" Votes="83" />

      <PCS:Option Name="George Lazenby" Votes="32" />

      <PCS:Option Name="Timothy Dalton" Votes="28" />

      <PCS:Option Name="Pierce Brosnan" Votes="95" />

     </PCS:StrawPoll>

     <br> <br>

     <asp:Label Runat= "server" ID="resultLabel" Text="No vote cast." />

    </form>

    вместе со связанным объявлением в скрытом коде формы, если он не добавляется автоматически:

    public class WebForm1 : System.Web.UI. Page {

     protected StrawPoll strawPoll1;

    Затем сделаем что-нибудь в самом обработчике событий:

     protected void strawPoll1_OnVoted(object sender, OptionEventArgs e) {

      result.Label.Text = "You voted for " + e.originatingOption.Name + ".";

     }

    Теперь, после голосования мы получаем ответную реакцию на наш голос:

    Заключение

    В этой главе были рассмотрены различные способы создания повторно используемых серверных элементов управления ASP.NET с помощью C#. Мы увидели, как создаются простые элементы управления пользователя из существующих страниц ASP.NET, а также специальные элементы.

    С помощью специальных элементов управления можно сделать много разных вещей, но, к сожалению, невозможно рассмотреть все это в одной главе. Однако здесь представлено достаточно информации, чтобы можно было начать. В частности, было бы интересно посмотреть на связывание данных и на то, как можно создавать свои собственные элементы, использующие эту возможность. Также было бы полезно рассмотреть более подробно некоторые области (например, расширить рассмотрение свойств и конструкторов элементов управления). Хотя такие вопросы лучше подходят для более специализированных книг.

    Эта глава завершает три главы, посвященные Web. Далее мы перейдем к рассмотрению взаимодействия COM и .NET.







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх