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

54

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

Все веб-элементы ASP.NET - это серверные элементы управления.

Серверные элементы управления - это классы .NET, унаследованные прямо или опосредованно от System.Web.UI.Control. Класс Control предоставляет свойства и методы, общие для всех серверных элементов управления (такие как ID, ViewState и коллекция Controls). Большинство элементов управления не наследуются прямо от Control; вместо этого они наследуются от System.Web.UI.WebControls.WebControl, который добавляет несколько средств, помогающих реализовать стандартные стили. К ним относятся такие свойства, как Font, ForeColor и BackColor.

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

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

Для построения новой сборки со специальными серверными элементами управления начните с создания нового проекта в Visual Studio, выбрав пункт меню File --> New Project. В диалоговом окне New Project перейдите в раздел Visual C# --> Web. Укажите в качестве типа проекта ASP.NET Server Control (Серверный элемент управления ASP.NET). Этот шаблон проекта - по сути такой же, как проект обычной сборки библиотеки классов, за исключением того, что он уже имеет необходимые ссылки на сборки ASP.NET.

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

Чтобы создать базовый специальный элемент управления, его необходимо унаследовать от класса Control и переопределить метод Render(). Метод Render() получает объект HtmlTextWriter, который используется для генерации HTML-разметки элемента управления.

Простейший способ генерации HTML-разметки - использование метода HtmlTextWriter.Write(), который записывает строку низкоуровневого HTML-кода на страницу. Очевидно, что метод Write() нельзя применять для вывода дескрипторов ASP.NET и другого содержимого серверной стороны, потому что содержимое конечной страницы визуализируется непосредственно перед ее отправкой клиенту.

Вот пример элемента управления, который генерирует простую гиперссылку с использованием HtmlTextWriter в методе Render():

public class LinkControl : Control
{
    protected override void Render(HtmlTextWriter writer)
    {
        writer.Write(
            "<a href='http://professorweb.ru'>Посетить сайт www.professorweb.ru</a>");
    }
}

Класс HtmlTextWriter не только позволяет записывать низкоуровневый HTML-код, но также предлагает некоторые полезные методы, которые помогают управлять атрибутами стиля и дескрипторами. В следующем примере представлен тот же самый элемент управления, но с парой небольших изменений. Во-первых, он визуализирует открывающий и закрывающий дескрипторы <a> отдельно с помощью методов RenderBeginTag() и RenderEndTag(). Во-вторых, он добавляет атрибуты, которые управляют внешним видом элемента управления. Ниже приведен полный код:

public class LinkControl : Control
{
    protected override void Render(HtmlTextWriter writer)
    {
        // Указать URL-адрес последующего дескриптора <a>
        writer.AddAttribute(HtmlTextWriterAttribute.Href, "http://professorweb.ru");

        // Добавить атрибуты стиля
        writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "20px");
        writer.AddStyleAttribute(HtmlTextWriterStyle.Color, "Blue");

        // Создать дескриптор <a>
        writer.RenderBeginTag(HtmlTextWriterTag.A);

        // Вывести текст внутри дескриптора
        writer.Write("Посетить сайт www.professorweb.ru");

        // Закрыть дескриптор
        writer.RenderEndTag();
    }
}

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

HtmlTextWriterTag

Это перечисление определяет десятки HTML-дескрипторов, таких как <a>, <p>, <div> и т.п.

HtmlTextWriterAttribute

Это перечисление определяет огромное множество общих атрибутов HTML-дескрипторов, таких как onClick, href, alt и т.д.

HtmlTextWriterStyle

Это перечисление определяет 14 атрибутов стиля, включая BackgroundColor, BackgroundImage, BorderColor, BorderStyle, BorderWidth, Color, FontFamily, FontSize, FontStyle, FontWeight, Height и Width. Все эти части информации объединяются в общий список, разделенный точками с запятой, который формирует информацию стиля CSS, используемого для установки атрибута style визуализируемого дескриптора.

Выполнение метода Render() начинается с определения всех атрибутов, которые будут добавлены к последующему дескриптору. Затем, когда создается открывающий дескриптор (методом RenderBeginTag()), все эти атрибуты помещаются в дескриптор. Финальный визуализированный дескриптор выглядит так:

<a href="http://professorweb.ru"
   style="font-size:20px; color:Blue">Посетить сайт www.professorweb.ru</a>

В таблице ниже приведен обзор ключевых методов HtmlTextWriter:

Методы класса HtmlTextWriter
Метод Описание
AddAttribute()

Добавляет любой HTML-атрибут и его значение в выходной поток HtmlTextWriter. Этот атрибут автоматически применяется для следующего дескриптора, который создается вызовом RenderBeginTag(). Вместо использования точного имени атрибута можно выбрать значение из перечисления HtmlTextWritterAttribute

AddStyleAttribute()

Добавляет атрибут стиля HTML и его значение к выходному потоку HtmlTextWriter. Этот атрибут автоматически применяется для следующего дескриптора, который создается вызовом RenderBeginTag(). Вместо использования точного имени стиля можно выбрать значение из перечисления HtmlTextWriterStyle, и оно будет визуализировано соответственно, в зависимости от возможностей браузера - будь то высокоуровневый или низкоуровневый клиент

RenderBeginTag()

Пишет открывающий дескриптор для HTML-элемента. Например, при формировании дескриптора привязки будет записано <a>. Вместо использования точного имени дескриптора можно выбрать значение из перечисления HtmlTextWriterTag

RenderEndTag()

Пишет закрывающий дескриптор для текущего HTML-элемента. Например, при формировании дескриптора привязки будет записано </a>. Указывать имя дескриптора не нужно

WriteBeginTag()

Этот метод подобен RenderBeginTag(), за исключением того, что он не пишет завершающий символ > для открывающего дескриптора. Это значит, что WriteBeginTag() можете вызывать для добавления дополнительных атрибутов к дескриптору. Чтобы закрыть открывающий дескриптор, следует вызвать Write(HtmlTextWriter.TagRightChar), который записывает символ >

WriteAttribute()

Пишет HTML-атрибут в выходной поток. Должен следовать за методом WriteBeginTag()

WriteEndTag()

Пишет закрывающий дескриптор для текущего HTML-элемента (последнего открытого с помощью метода WriteBeginTag())

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

Чтобы использовать специальный элемент управления, его необходимо сделать доступным для веб-приложения. При этом есть два выбора: можно скопировать исходный код в каталог App_Code или же скомпилировать его в отдельную сборку, которую затем поместить в каталог Bin (используя пункт меню Add Reference (Добавить ссылку) в Visual Studio).

Чтобы страница получила доступ к специальному элементу управления, необходимо применить директиву Register, как это делалось с пользовательскими элементами управления. Однако на этот раз должна быть определена слегка отличающаяся информация. Понадобится не только включить TagPrefix, но также указать файл сборки (без расширения .dll) и пространство имен, в котором находится класс элемента управления. Задавать TagName не нужно, поскольку имя класса серверного элемента управления используется автоматически.

Ниже показан пример директивы Register:

<%@ Register TagPrefix="professorweb" Namespace="Professorweb.Controls"
     Assembly="SimpleControls" %>

Если элемент управления находится в каталоге App_Code текущего веб-приложения, то включать атрибут Assembly не понадобится.

Префиксы дескриптора можно использовать повторно. Другими словами, совершенно корректно отображать два разных пространства имен или две совершенно разные сборки на один и тот же префикс дескриптора.

Если необходимо использовать элемент управления на нескольких страницах одного и того же веб-приложения, в ASP.NET предусмотрено удобное сокращение - регистрация префикса дескриптора в файле web.config, как показано ниже:

<configuration>
    <system.web>
      <pages>
        <controls>
          <add tagPrefix="professorweb" namespace="Professorweb.Controls"
               assembly="SimpleControls"/>
        </controls>
      </pages>
    </system.web>
</configuration>

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

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

<body>
    <form id="form1" runat="server">
        <div>
            <professorweb:LinkControl ID="LinkControl1" runat="server" />
        </div>
    </form>
</body>

На рисунке ниже показан специальный элемент LinkControl в действии:

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

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

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

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

Для тестирования элементов управления должно использоваться другое приложение. Доступны два подхода. При первом из них можно добавить ссылку точно таким же образом, как добавляется ссылка на любую другую сборку .NET. Просто щелкните правой кнопкой мыши на веб-сайте в Solution Explorer и выберите в контекстном меню пункт Add Reference (Добавить ссылку). Перейдите на вкладку Projects (Проекты), выберите только что созданный проект специального элемента управления и щелкните на кнопке ОК. Это скопирует скомпилированную сборку элементов управления в каталог Bin веб-сайта, сделав ее доступной для страниц.

Более легкий подход состоит в использовании автоматической поддержки панели инструментов в Visual Studio. Когда компилируется проект, содержащий специальные серверные элементы управления, Visual Studio проверяет каждый элемент управления и добавляет каждый их них во временный, специфичный для проекта раздел панели инструментов, расположенный вверху. Это значит, что можно легко добавлять элементы управления на любую страницу. При перетаскивании элемента управления на страницу Visual Studio автоматически копирует сборку в каталог Bin, если только уже не создана ссылка, добавляет директиву Register, если она еще не представлена на странице, и, наконец, добавляет дескриптор элемента управления:

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

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

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

Для этого щелкните правой кнопкой мыши в панели инструментов и выберите в контекстном меню Toolbox пункт Choose Items (Выбрать элементы). На вкладке .NET Framework Components (Компоненты .NET Framework) щелкните на кнопке Browse (Обзор). Затем выберите сборку со специальным элементом управления в браузере файлов. Элементы управления будут добавлены в список доступных элементов управления .NET, как показано на рисунке ниже:

Добавление в панель инструментов специального элемента управления

Все отмеченные элементы управления появятся в панели инструментов. Обратите внимание, что элементы не добавляются в каждый проект по отдельности. Вместо этого они остаются в панели инструментов до тех пор, пока не будут удалены. Для удаления элемента управления щелкните на нем правой кнопкой мыши и выберите в контекстном меню пункт Delete (Удалить). Это действие удалит только значок, но не ссылаемую сборку.

Среда Visual Studio предлагает некоторую базовую поддержку времени проектирования. Например, после того, как специальный элемент управления добавлен на веб-страницу, в окне Properties (Свойства) можно модифицировать его свойства (которые отображаются в группе Misc) и присоединять обработчики событий.

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

Создание веб-элемента управления, поддерживающего свойства стиля

В предыдущем примере специального элемента управления веб-странице не разрешалось изменять его внешний вид. LinkControl не предоставляет никаких свойств для установки цветов фона и переднего плана, шрифта или других атрибутов генерируемого HTML-дескриптора. Другими словами, LinkControl - готовый элемент управления, с фиксированной визуализацией, который не позволяет внешнему коду (веб-странице} изменять HTML-разметку, которую он генерирует.

Чтобы сделать LinkControl более гибким, необходимо явно добавить общедоступные свойства для различных связанных с форматированием деталей. Затем потребуется прочитать эти свойства в методе Render() и сгенерировать соответствующий HTML-код.

Естественно, свойства стиля являются базовой частью инфраструктуры, которую нужно использовать многим элементам управления HTML. В идеале все элементы управления должны следовать одной прямолинейной модели информации стиля и не вынуждать разработчиков элементов управления разрабатывать общую функциональность самостоятельно. ASP.NET обеспечивает это через базовый класс WebControl (из пространства имен System.Web.UI.WebControls). Каждый веб-элемент управления, включенный в ASP.NET, унаследован от WebControl, и от него можно также наследовать собственные специальные элементы управления.

Но класс WebControl не только включает в себя базовые свойства, относящиеся к стилю, такие как Font, ForeColor, BackColor и т.д., но также визуализирует их автоматически в дескрипторе элемента управления. Вот как это работает. WebControl предполагает, что он должен добавить атрибуты к одному HTML-дескриптору, именуемому базовым дескриптором. Если записывается множество элементов, атрибуты добавляются к самому внешнему элементу, содержащему в себе прочие. Базовый дескриптор для элемента управления указывается в конструкторе.

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

Конечно, при необходимости это поведение можно изменить, переопределив метод Render(). Но если эта базовая структура соответствует существующим потребностям, очень многого можно достичь, добавляя лишь небольшой объем специального кода.

В следующем примере демонстрируется новый элемент управления ссылкой, унаследованный от WebControl и таким образом получающий автоматическую поддержку свойств стиля. Конструктор по умолчанию вызывает конструктор WebControl. Существует более одной версии WebControl - в этом коде используется версия, которая позволяет указывать дескриптор базового элемента управления. В данном примере базовым дескриптором элемента управления является <a>, как показано ниже:

public class LinkControl : WebControl
{
    public LinkControl() : base(HtmlTextWriterTag.A)
    { }

    // ...
}

Конструктор LinkControl не требует никакого действительного кода. Важно, чтобы эта возможность использовалась для вызова конструктора WebControl с целью установки дескриптора базового элемента управления. Если применяется конструктор WebControl по умолчанию (без параметров), то дескриптор <span> будет использован автоматически. Затем внутри этого дескриптора <span> можно визуализировать дополнительную HTML-разметку, гарантирующую одинаковые атрибуты стиля для всех элементов.

В LinkControl также определены два свойства, которые позволяют веб-странице устанавливать текст и целевой URL-адрес:

public class LinkControl : WebControl
{
    public LinkControl() : base(HtmlTextWriterTag.A)
    { }

    private string text; 
    public string Text 
    {
        get {return text;} 
        set {text = value;}
    }
    
    private string hyperLink; 
    public string HyperLink
    {
        get {return hyperLink;}
        set {
            if (value.IndexOf("http://") == -1)
            {
                // В качестве протокола должен быть указан HTTP
                throw new ApplicationException("Некорректный URL сайта");
            }
            else
                hyperLink = value;
        }
    }

    // ...
}

Переменные text и hyperLink можно было бы установить в пустые строки при определении. Однако в этом примере переопределяется метод OnInit() для демонстрации того, как можно инициализировать элемент управления программно:

protected override void OnInit(EventArgs e)
{
        base.OnInit(e);
        // Если в дескрипторе элемента управления значения не установлены, 
        // применить значения по умолчанию
        if (hyperLink == null)
            hyperLink = "http://www.google.com"; 
        if (text == null)
            text = "Поиск";
}

Элемент LinkControl привносит некоторую сложность. Чтобы успешно создать дескриптор <a>, нужно указать целевой URL-адрес и некоторый текст. Текст помещается между открывающим и закрывающим дескрипторами. Однако URL-адрес добавляется как атрибут (по имени href) к открывающему дескриптору. Как уже известно, WebControl управляет атрибутами открывающего дескриптора автоматически. К счастью, класс WebControl предоставляет возможность добавлять дополнительные дескрипторы, переопределяя метод AddAttributesToRender():

protected override void AddAttributesToRender(HtmlTextWriter writer)
{
        writer.AddAttribute(HtmlTextWriterAttribute.Href, HyperLink);
        base.AddAttributesToRender(writer);
}

Обратите внимание, что после того как специальный элемент управления переопределяет метод, он может вызывать реализацию базового класса, используя ключевое слово base. Такой вызов гарантирует, что не будет утерян какой-либо код, который должен быть выполнен. Часто все, что делает базовый метод - это инициирует соответствующее событие, хотя и не всегда. Например, если переопределить RenderBeginTag() и не вызвать базовую реализацию, то код визуализации даст сбой с генерацией необработанного исключения, потому что дескриптор не будет открыт.

Наконец, метод RenderContents() добавляет текст внутри <a>:

protected override void RenderContents(HtmlTextWriter writer)
{
        writer.Write(Text);
        base.RenderContents(writer);
}

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

Созданный элемент управления можно использовать на любой веб-странице ASP.NET. Свойства стиля могут быть установлены в коде или в дескрипторе элемента управления. Можно даже использовать окно Properties. Ниже приведен пример:

<body>
    <form id="form1" runat="server">
        <div>
            <professorweb:LinkControl ID="LinkControl1" runat="server"
                 BackColor="White" Font-Names="Calibri" Font-Size="Large"
                 ForeColor="LimeGreen" HyperLink="http://professorweb.ru"
                 Text="www.professorweb.ru - программирование на языке C# и платформа .Net Framework" />
        </div>
    </form>
</body>

Атрибуты HyperLink и Text автоматически отображаются на соответствующие общедоступные свойства специального элемента управления. То же касается и связанных со стилем свойств, определенных в базовом классе WebControl. На рисунке ниже показан этот элемент управления в веб-браузере:

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

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

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

Когда создается проект серверного элемента управления ASP.NET, он начинается с одного серверного элемента управления (с довольно невразумительным именем WebCustomControl1). Создать дополнительные элементы управления можно за счет добавления новых файлов кода и написания кода вручную. Или же можно создать новый элемент управления с небольшой помощью от Visual Studio, для чего выбрать пункт меню Projects Add New Item {Проект --> Добавить новый элемент), перейти в раздел Visual C# Items --> Web (Элементы Visual C# --> Веб) и указать шаблон ASP.NET Server Control (Серверный элемент управления ASP.NET).

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

Все эти детали довольно легко добавить и без помощи Visual Studio, так что не бойтесь начать с пустого файла кода и написать класс специального элемента управления вручную (именно так и поступают многие разработчики элементов управления).

Процесс визуализации

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

Начальной точкой процесса визуализации является метод RenderControl(). Этот метод представляет собой общедоступный метод визуализации, который использует ASP.NET для визуализации каждого элемента управления на веб-странице в виде HTML-разметки. Переопределять метод RenderControl() не следует. Вместо этого RenderControl() вызывает защищенный метод Render(), запускающий процесс визуализации. Как было продемонстрировано в первом примере, переопределять Render() можно. Однако если метод Render() переопределен, но при этом не вызвана его реализация из базового класса, прочие методы визуализации вызываться не будут.

Как было показано в предыдущем примере, базовая реализация метода Render() вызывает RenderBeginTag(), RenderContents() и затем RenderEndTag(). Тем не менее, здесь имеется еще один нюанс. Базовая реализация метода RenderContents() вызывает другой метод визуализации - RenderChildren(). Этот метод проходит в цикле по коллекции дочерних элементов управления Controls и вызывает метод RenderControl() для каждого индивидуального элемента управления. Пользуясь преимуществом такого поведения, можно легко строить элемент управления из других элементов управления.

Какой же метод визуализации должен быть переопределен? Если требуется заменить весь процесс визуализации каким-нибудь новым или добавить HTML-содержимое перед дескриптором базового элемента управления (например, блок кода JavaScript), можно переопределить метод Render(). Если вы хотите воспользоваться преимуществом автоматических атрибутов стиля, то должны определить базовый дескриптор (указывая параметр имени дескриптора, такой как HtmlTextWriterTag.A, при вызове базового конструктора) и затем переопределить метод RenderContents(). Если нужно предотвратить отображение дочерних элементов управления или настроить их визуализацию (например, выполняя ее в обратном порядке), то можно переопределить RenderChildren().

На рисунке ниже показан итоговый процесс визуализации:

Методы визуализации элемента управления

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

protected void Page_Load(object sender, EventArgs e)
{
        // Создать объекты в памяти, которые перехватывают вывод визуализации
        StringWriter writer = new StringWriter();
        HtmlTextWriter output = new HtmlTextWriter(writer);

        // Визуализировать элемент управления в строку, находящуюся в памяти
        LinkControl1.RenderControl(output);

        // Отобразить HTML-разметку (и правильно закодировать ее,
        // чтобы она появлялась как текст в браузере)
        Label1.Text = "HTML-разметка элемента управления LinkControl: <blockquote>" +

        Server.HtmlEncode(writer.ToString()) + "</blockquote>";
}

На рисунке ниже показана страница с элементом управления и его HTML-разметкой:

Получение HTML-представления элемента управления

Этот прием предназначен не только для отладки. Он также используется для упрощения кода визуализации. Например, может быть проще создать и сконфигурировать элемент управления HtmlTable и затем вызвать его метод RenderControl() вместо записи таких дескрипторов, как <table>, <td> и <tr>, непосредственно в выходной поток.

Лучший чат для C# программистов