Программирование на ASP.NET

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

Разбить на страницы
Показывать лекцию целиком
Файлы к лекции Вы можете скачать здесь

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

Пользовательский элемент управления представляет собой небольшую часть страницы, которая может содержать статический HTML-код и стандартные элементы управления. В пользовательских элементах управления можно предусмотреть свои свойства, методы и события. Пользовательские блоки кода могут многократно использоваться на разных страницах, но только одного сайта. Для применения в другом сайте его либо нужно скопировать в этот сайт, либо создавать библиотечные специальные серверные элементы наподобие стандартных ( custom server control ).

Файл пользовательского элемента управления может содержать то же самое, что и файл обычной исполнимой страницы, включая файл поддержки .cs, статический HTML-код, стандартные элементы управления. Они получают те же самые события, что и объект Page, имеют доступ к таким важным объектам-свойствам, как Application, Session, Request, Response. Но есть и существенные отличия:

  • В отличие от страниц, файлы которых имеют расширение .aspx, пользовательские элементы управления должны храниться в файлах с расширением .ascx
  • Объект страницы произведен от класса
  • Среда исполнения не может напрямую вызвать файл .ascx, а должна это делать через .aspx, в который внедряется ссылка на файл .ascx
  • Файл пользовательского элемента управления не является отдельной страницей, поэтому не должен содержать дескрипторы <html>, <head>, <body>, <form>
  • Вместо директивы @Page файл пользовательского элемента управления содержит директиву @Control
  • Среда проектирования позволяет видеть внедренный в страницу пользовательский элемент управления на поверхности конструктора Design страницы.

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

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

  • Выполните команду Website/Add New Item, выберите шаблон Web User Control и задайте имя Header.ascx
  • Посмотрите, что создал мастер. Файл .ascx содержит только одну строку директивы

    <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Header.ascx.cs" Inherits="Header" %>

    Пользовательский элемент управления редактируется в оболочке точно также, как и обычная страница. Файл поддержки .ascx.cs содержит тот же код, что и для обычной страницы. В данном примере он нам не нужен, поэтому ничего в нем делать не будем (пусть болтается пустой).

  • Поместите в файл Header.ascx следующую HTML-разметку
  • <table width="100%"  border="0" bgcolor="blue">
      <tr>
        <td>
    	<font face="Vernada, Arial" size="6" color="yellow">
    	   <b>Тестовая страница User Control</b>
    	</font>	</td>
      </tr>
      <tr>
        <td align="right">
    	  <font size="3" color="white">
    	    <b>Время создания: март 2007г. copy; Все права защищены</b>
        </font>	</td>
      </tr>
    </table>

    Эта разметка даст такое визуальное представление

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

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

  • Создайте новую страницу с именем HeaderTest.aspx для внедрения в нее пользовательского элемента управления
  • Чтобы использовать элемент управления на странице, нужно интегрировать его в форму. Для этого нужно выполнить следующие шаги:

  • Зарегистрировать элемент на странице, т.е. с помощью директивы @Register, помещенной в начале страницы, нужно сообщить ASP.NET, что где-то на странице есть ссылка на пользовательский элемент управления.
  • Поместить пользовательский элемент управления на страницу, вставив в нужное место внутри дескриптора <form> ссылку-тег на этот элемент.
  • Директива @Register для нашего примера имеет следующий вид

    <%@ Register TagPrefix="myElem" TagName="Header" Src="Header.ascx" %>

    Мы знаем, что для различения стандартных элементов управления в начале их дескрипторного представления используется конструкция <префикс:тип_элемента атрибуты... />, например

    <asp:TextBox ID="TextBox1" runat="server" />

    Здесь asp играет роль префикса TagPrefix (или условного пространства имен), а TextBox - тип элемента управления TagName. Директива регистрации также для лучшего различения пользовательских элементов управления поддерживает одинаковый синтаксис. Значения myElem и Header мы задали произвольно, но далее эти значения на странице мы должны строго соблюдать, а значение атрибута Src ( Source - источник) означает подключаемый файл с пользовательским элементом управления.

    В зарегистрированных обозначениях пользовательского элемента управления синтаксис его подключения к странице HeaderTest.aspx будет таким (ну, как обычно!):

    <myElem:Header ID="Header1" runat="server" ></myElem:Header>

    или таким

    <myElem:Header ID="Header1" runat="server" />
    В Visual Studio 2005 нам не нужно руками прописывать приведенную регистрацию. Достаточно в окне Solution Explorer перенести внедряемый файл Header.ascx в нужное место страницы и подправить, при необходимости, сгенерированную оболочкой регистрацию.

    В результате пользовательская страница HeaderTest.aspx должна иметь такую HTML-разметку

    <%@ Page AutoEventWireup="true" CodeFile="HeaderTest.aspx.cs" Inherits="HeaderTest"
        Language="C#" %>
    <%@ Register TagPrefix="myElem" TagName="Header" Src="Header.ascx" %>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Тест элемента user control</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <myElem:Header ID="Header1" runat="server" />
            </div>
        </form>
    </body>
    </html>

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

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

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

    Обработка событий

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

  • Вызовите мастер для создания пользовательского элемента управления, выполнив команду Website/Add New Item. Установите шаблон в значение Web User Control, сбросьте флажок Place code in separate file и задайте имя файла TimeDisplay.ascx
  • Поместите в редактор Design стандартный элемент управления LinkButton из вкладки Standard
  • Через панель Properties измените ID элемента на lnkTime
  • Двойным щелчком на свободном месте редактора Design создайте обработчик Page_Load()
  • При выделенном элементе lnkTime установите панель Properties в режим Events (кнопка с изображением молнии вверху панели), найдите событие Click и двойным щелчком на поле его значения создайте обработчик lnkTime_Click()
  • Заполните файл следующим кодом
  • <%@ Control Language="C#" ClassName="TimeDisplay" %>
    	
    <script runat="server">
    	
        protected void Page_Load(object sender, EventArgs e)
        {
            RefreshTime();
        }
    	
        protected void lnkTime_Click(object sender, EventArgs e)
        {
            RefreshTime();
        }
    	
        public void RefreshTime()
        {
            lnkTime.Text = DateTime.Now.ToLongTimeString();
        }
    </script>
    
    <asp:LinkButton ID="lnkTime" runat="server" OnClick="lnkTime_Click" />

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

  • Чтобы протестировать наш код, добавьте к приложению новую страницу с именем TimeDisplayTest.aspx
  • Перетащите на нее из панели Solution Explorer файл TimeDisplay.ascx с пользовательским элементом управления
  • Выполните страницу, которая на клиенте будет такой
  • При каждом щелчке пользователя на ссылке-кнопке она будет снимать (и отображать) с таймера сервера новое значение времени.

    Понятие свойства класса

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

  • типа, равного типу поля, доступом к которому оно управляет
  • из имени с модификатором доступа, по которому будут читаться или писаться значения поля, контролируемые свойством
  • из пары аксессоров get и set, содержащих контролирующий поле код
  • Основное достоинство свойства состоит в том, что его можно использовать в выражениях и инструкциях присваивания как обычную переменную-член класса, хотя на самом деле здесь автоматически будут вызываться get - и set -аксессоры. Синтакис свойства таков:

    модификатор_доступа тип имя_свойства
    	{ 
    		get
    		{
    			// код аксессора чтения поля
    			............................
    			return поле;
    		}
    		set
    		{
    			// код аксессора записи поля
    			............................
    			поле = value;
    		}
    	}

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

    class MyClass
    {
    	public MyClass()
    	{
    		xField = 0;
    	}
    	
    	private int xField; // Это поле будет управляться свойством x
    
    	// Это свойство поддерживает доступ к закрытому полю xField
    	// через имя доступа x, равное имени свойства
    	// Для клиента этого класса x видна как переменная
    	public int x
    	{
    		get
    		{
    			return xField;
    		}
    		set
    		{
    			// Переменная value - зарезервированное имя переменной
    			// обобщенного типа, принимающей значение от клиента
    			// Это имя менять нельзя
    			if(value >= 0)
    				xField = value;
    		}
    	}
    }

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

    Добавление свойств

    Вернемся к нашей теме разработки пользовательского элемента управления. Расширим возможности предыдущего примера. Добавим в код общедоступное свойство Format, которому в клиенте (использующая элемент страница) будем передавать либо пустую строку, либо строку форматирования. В зависимости от этого пользовательский элемент управления будет способен отображать надпись на кнопке LinkButton в разных форматах.

  • Измените код файла TimeDisplay.ascx следующим образом
    (рис ) Улучшенный код файла TimeDisplay.ascx<%@ Control Language="C#" ClassName="xx" %>
    	
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            RefreshTime();
        }
    	
        private string format; // Контролируемое свойством поле
        public string Format   // Определение свойства Format
        {
            get { return format; }
            set { format = value; }
        }
    	
        protected void lnkTime_Click(object sender, EventArgs e)
        {
            RefreshTime();
        }
    	
        public void RefreshTime()
        {
            if (format == "")
                lnkTime.Text = DateTime.Now.ToLongTimeString();
            else // Значение format задаем в клиенте через свойство Format
                lnkTime.Text = DateTime.Now.ToString(format);
        }
    </script>
    
    <asp:LinkButton ID="lnkTime" runat="server" OnClick="lnkTime_Click" />
  • Измените код тестовой страницы TimeDisplayTest.aspx следующим образом
    (рис ) Код тестовой страницы TimeDisplayTest.aspx<%@ Page Language="C#" %>
    	
    <%@ Register Src="TimeDisplay.ascx" TagName="TimeDisplay" TagPrefix="uc1" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница TimeDisplayTest</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <uc1:TimeDisplay ID="TimeDisplay1"
                    runat="server"
                    Format="" />
                <br />
                <br />
                <hr />
                <br />
                <uc1:TimeDisplay ID="TimeDisplay2" 
                    runat="server"
                    Format="Сегодня dddd, dd MMMM yyyy<br />Текущее время HH:mm:ss" />
            </div>
        </form>
    </body>
    </html>
  • Тестовая страница является клиентом пользовательского элемента управления. Именно в дескрипторе элемента на клиенте мы определяем значение добавленного свойства Format как атрибут этого дескриптора. Обратите внимание, что в подсказчике кода IntelliSense, применяемом при редактировании дескриптора пользовательского элемента управления, появилось новое свойство Format.

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

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

    Порядок генерации событий инициализации

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

  • Производится удаленный запрос страницы и она загружается в память сервера
  • Создается и инициализируется конструктором класса пользовательский элемент управления
  • Присваиваются значения атрибутов, предусмотренные в дескрипторе пользовательского элемента управления
  • Создается объект родительской страницы
  • Выполняется событие Page.Load родительской страницы, в обработчике Page_Load() которого можно предусмотреть переназначение атрибутов-свойств пользовательского элемента управления
  • Выполняется событие Page.Load пользовательского элемента управления
  • Отсюда важный вывод, что если мы будем инициализировать пользовательский элемент управления в его обработчике Page_Load(), то он перезапишет все свои настройки, выполненные в родительском коде, поскольку срабатывает последним. Так, если бы в последнем примере обработчик Page_Load() пользовательского элемента управления инициализировал поле format и имел бы вид

    protected void Page_Load(object sender, EventArgs e)
    {
        format = "";
        if (!Page.IsPostBack)
            RefreshTime();
    }

    то мы бы всегда получали результат варианта без форматирования, а именно

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

    Иногда приходится создавать специальные классы для организации взаимодействия между пользовательским элементом управления и Web-страницей. Рассмотрим пример, в котором создадим сложный пользовательский элемент управления LinkTable, предназначенный для генерирования набора гиперссылок в форматированной таблице. Для удобства часть кода разместим в отдельном самостоятельном классе LinkTableItem.

  • Создайте страницу Web Form с раздельным кодом и именем LinkTableTest.aspx
  • Сделайте эту страницу стартовой
  • Добавьте к проекту новую папку с зарезервированным именем
  • Через контекстное меню на созданной папке выполните команду
  • Через окно мастера добавьте к приложению новый класс C# с именем Этот класс в своих полях будет хранить информацию, необходимую пользовательскому элементу управления, а через свойства осуществлять доступ к ней.
  • Заполните вспомогательный класс в файле LinkTableItem.cs следующим кодом
    (рис ) Код файла App_Code/LinkTableItem.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    /// <summary>
    /// Вспомогательный класс LinkTableItem
    /// </summary>
    public class LinkTableItem
    {
        // Хранимые поля закрытого типа
        private string text, url;
    	
        // Конструктор по умолчанию
    	public LinkTableItem()
    	{
            // Пустой конструктор по умолчанию обязателен
            // поскольку есть общий конструктор
    	}
    	
        // Общий конструктор
        public LinkTableItem(string text, string url)
        {
            // Внутренним полям присваиваются значения,
            // переданные из родительского объекта
            this.text = text;
            this.url = url;
        }
    	
        // Определение свойств доступа к внутренним полям
        public string Text
        {
            get { return text; }
            set { text = value; }
        }
    	
        public string Url
        {
            get { return url; }
            set { url = value; }
        }
    }
  • Теперь нужно создать пользовательский элемент, который будет управлять этим вспомогательным классом.

  • Создайте заготовку пользовательского элемента управления
  • Поместите в таблицу пользовательского элемента следующие стандартные компоненты:
  • Table из вкладки HTML, который будет форматировать размещение стандартных элементов управления
  • Label из вкладки Standard в верхнюю ячейку таблицы, который будет определять заголовок набора ссылок
  • Image из вкладки HTML, определяющий рисунок маркера ссылок
  • DataList из вкладки Data в нижнюю ячейку таблицы
  • HyperLink из вкладки Standard
  • Настройте дескрипторный код пользовательского элемента управления так
  • <%@ Control Language="C#" AutoEventWireup="true" CodeFile="LinkTable.ascx.cs" 
    Inherits="LinkTable" %>
    	
    <table border="1" cellpadding="2" cellspacing="0" width="100%">
        <tr>
            <td>
                <asp:Label ID="lblTitle" runat="server"
                    ForeColor="#C00000" Font-Bold="true" 
                    Font-Names="Vernada" Font-Size="Small">
                    <!--Здесь будет заголовок-->
                </asp:Label>
            </td>
        </tr>
        <tr>
            <td>
                <asp:DataList ID="listContent" runat="server">
                    <ItemTemplate>
                        <img width="25" height="13" src="Yes.gif" alt="маркер">
                        <asp:HyperLink ID="HyperLink1" runat="server"
                            NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "Url") %>'
                            Text='<%# DataBinder.Eval(Container.DataItem, "Text") %>' />
                    </ItemTemplate>
                </asp:DataList>
            </td>
        </tr>
    </table>

    Дескриптор ). Атрибут alt задает альтернативную надпись в поле рисунка, если последний не будет найден.

    Теперь пришла пора расширить функциональность пользовательского элемента управления LinkTable. Для этого

  • Вызовите на редактирование файл LinkTable.ascx.cs и заполните его следующим кодом
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTable : System.Web.UI.UserControl
    {
        public string Title
        {
            get { return lblTitle.Text; }
            set { lblTitle.Text = value; }
        }
    	
        private LinkTableItem[] items;
        public LinkTableItem[] Items
        {
            get { return items; }
            set 
            {
                items = value;
    	
                // Обновляем сетку
                listContent.DataSource = items;
                listContent.DataBind();
            }
        }
    }

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

  • Выполните команду Window/Close All Documents, чтобы закрыть все окна редактирования
  • Откройте через панель Solution Explorer на редактирование файл LinkTableTest.aspx в режиме Design и перетащите на форму из этой панели узел LinkTable.ascx пользовательского элемента управления
  • После этого форма страницы должна выглядеть так

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

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="LinkTableTest.aspx.cs" Inherits="LinkTableTest" %>
    	
    <%@ Register Src="LinkTable.ascx" TagName="LinkTable" TagPrefix="uc1" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Тестовая страница сложного элемента управления</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <uc1:LinkTable ID="LinkTable1" runat="server" />
            </div>
        </form>
    </body>
    </html>
  • Откройте на редактирование файл поддержки страницы LinkTableTest.aspx.cs и заполните его следующим кодом
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTableTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Формируем заголовок списка
            LinkTable1.Title = "Список жизненно важных гиперссылок";
    	
            // Создаем список элементов - гиперссылок
            LinkTableItem[] items = new LinkTableItem[3];
            items[0] = new LinkTableItem("Ссылка №1 к Большому Биллу", 
                "http://www.microsoft.com");
            items[1] = new LinkTableItem("Ссылка №2 к автошколе \"Диалог-Сервис\"", 
                "http://www.dialog-service.net");
            items[2] = new LinkTableItem("Ссылка №3 к затычке JavaScript", 
                "javascript:void(0)");
            LinkTable1.Items = items;
        }
    }
  • Запустите приложение и убедитесь, что генерируемый пользовательским элементом управления набор гиперссылок работает исправно
  • Добавление событий

    Событие - это встроенный механизм C#, с помощью которого автоматически обеспечивается уведомление зарегистрированных объектов о необходимости выполнения ими какого-то действия. Объект предоставляет свои методы-обработчики для реагирования на возникновение определенного события. В этом случае говорят, что объект подписался на некоторое событие. Такой механизм позволяет отдельным объектам обмениваться сообщениями и реагировать на них.

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

    Для объявления адресной переменной события используются делегаты. Делегаты представляются специальным классом, определяющим сигнатуру (типы, количество и порядок следования аргументов) передаваемой с событием информации отправляющей стороной, и сигнатуру обработчиков события принимающей стороны. Делегат объявляется в глобальной области видимости уровня класса, чтобы сделать событие видимым во всех классах приложения. Объект, порожденный классом-делегатом, и является событием.

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

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

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

  • В панели Solution Explorer, удерживая клавишу Ctrl, выделите узлы LinkTable.ascx и LinkTableTest.aspx
  • Через контекстное меню панели выполните команду Copy, а затем через контекстное меню на узле проекта (вверху) выполните команду Paste
  • Переименуйте созданные копии в имена оригиналов, но с добавлением Ext (extension - расширенный), а именно: LinkTableExt.ascx, LinkTableExtTest.aspx (файлы поддержки LinkTableExt.ascx.cs и LinkTableExtTest.aspx.cs оболочка переименует сама)
  • Сделайте страницу LinkTableExtTest.aspx стартовой, выполнив на ее узле в представлении панели Solution Explorer команду Set As Start Page контекстного меню
  • Подправьте в файле LinkTableExtTest.aspx декларацию @Register, чтобы новая тестовая страница применяла пользовательский элемент управления из другого файла
    <%@ Register Src="LinkTableExt.ascx" TagName="LinkTable" TagPrefix="uc1" %>
  • Запустите новую тестовую страницу на выполнение, чтобы убедиться в ее работоспособности с прежней функциональностью
  • Все, теперь мы готовы модернизировать новые файлы предыдущего примера. Посмотрим, что мы хотим сделать.

    В предыдущем примере мы с помощью вспомогательного класса LinkTableItem могли добавлять в классе пользовательского элемента управления LinkTableTest новые гиперссылки типа asp:HyperLink к элементу asp:DataList. По щелчку клиента на одной из гиперссылок он безусловно направлялся по указанному адресу. Теперь же мы хотим перехватывать и распознавать щелчок на любой гиперссылке для контроля за действиями пользователя: он хочет туда-то, а можно ли ему. Вдруг пользователь хочет в закрома, а мы считаем, что он не имеет на это достаточных прав. Сейчас мы не будем оценивать пользователя, просто распознаем гиперссылку и выведем ее на экран.

    Ранее в дескрипторном представлении пользовательского элемента управления мы применяли стандартный элемент asp:HyperLink. Если посмотреть на класс этого элемента System.Web.UI.WebControls.HyperLink через панель Object Browser, то можно увидеть, что он не имеет никаких событий

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

    Эти события мы и будем перехватывать в нашем элементе управления.

  • Измените тип элемента в шаблоне <ItemTemplate> дескрипторного представления файла LinkTableExt.ascx и новое имя атрибута CommandArgument, после чего файл должен быть таким
  • <%@ Control Language="C#" AutoEventWireup="true" CodeFile="LinkTableExt.ascx.cs" 
    Inherits="LinkTable" %>
    	
    <table border="1" cellpadding="2" cellspacing="0" width="100%">
        <tr>
            <td>
                <asp:Label ID="lblTitle" runat="server"
                    ForeColor="#C00000" Font-Bold="true" 
                    Font-Names="Vernada" Font-Size="Small">
                    <!--Здесь будет заголовок-->
                </asp:Label>
            </td>
        </tr>
        <tr>
            <td>
                <asp:DataList ID="listContent" runat="server">
                    <ItemTemplate>
                        <img width="25" height="13" src="Yes.gif" alt="маркер">
                        <asp:LinkButton ID="HyperLink1" runat="server" 
                            CommandArgument='<%# DataBinder.Eval(Container.DataItem, "Url") %>'
                            Text='<%# DataBinder.Eval(Container.DataItem, "Text") %>'>
                        </asp:LinkButton>
                    </ItemTemplate>
                </asp:DataList>
            </td>
        </tr>
    </table>

    Мы хотим создать событие с именем LinkClicked. Для распознавания выбранного пункта списка нам вместе с будущим событием LinkCliked нужно будет передавать информацию о значениях свойств Text и Url, которые мы определили ранее во вспомогательном классе LinkTableItem.cs. Затем в обработчике события LinkClicked мы распознаем эту информацию и выведем ее на экран.

    Напрямую экземпляр класса LinkTableItem.cs в аргументах события передавать нельзя - не тот тип, поэтому упакуем его в класс LinkTableEventArgs, который обязан быть наследником библиотечного класса System.EventArgs. Объявим также делегата события в глобальной области видимости.

  • Откройте файл LinkTableExt.ascx.cs поддержки пользовательского элемента управления и добавьте в него объявление нового класса и делегата, после чего общий код должен быть таким
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTable : System.Web.UI.UserControl
    {
        public string Title
        {
            get { return lblTitle.Text; }
            set { lblTitle.Text = value; }
        }
    	
        private LinkTableItem[] items;
        public LinkTableItem[] Items
        {
            get { return items; }
            set 
            {
                items = value;
    	
                // Обновляем сетку
                listContent.DataSource = items;
                listContent.DataBind();
            }
        }
    }
    	
    // Объявляем делегат события LinkClicked
    // в глобальной области видимости,
    // который определит сигнатуру события и обработчика
    public delegate void LinkClickedEventHandler(object sender, LinkTableEventArgs e);
    	
    // Класс-упаковка экземпляра LinkTableItem для 
    // передачи с событием в качестве аргумента
    public class LinkTableEventArgs : EventArgs
    {
        // Конструктор
        public LinkTableEventArgs(LinkTableItem item)
        {
            selectedItem = item;
        }
    	
        // Внутреннее поле будет хранить объект гиперссылки
        // и контролируется свойством "только для чтения"
        private LinkTableItem selectedItem;
        public LinkTableItem SelectedItem
        {
            get { return selectedItem; }
        }
    	
        // Внутренне поле хранит флаг отмены, переданный сгенерировавшим 
        // событие классом-источником для обработчика класса-приемника
        private bool cancel = false;// Инициализируется при создании
        public bool Cancel
        {
            get { return cancel; }
            set { cancel = value; }
        }
    }

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

  • Откройте на редактирование файл LinkTableExt.ascx в режиме Design
  • Выделите пользовательский элемент управления и в панели Properties создайте обработчик для события
  • Добавьте в класс LinkTable и созданный обработчик стандартного события следующий код
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTable : System.Web.UI.UserControl
    {
        public string Title
        {
            get { return lblTitle.Text; }
            set { lblTitle.Text = value; }
        }
    	
        private LinkTableItem[] items;
        public LinkTableItem[] Items
        {
            get { return items; }
            set 
            {
                items = value;
    
                // Обновляем сетку
                listContent.DataSource = items;
                listContent.DataBind();
            }
        }
    	
        // Объявляем в классе наше событие LinkClicked 
        // с помощью ранее созданного глобального делегата
        public event LinkClickedEventHandler LinkClicked;
    	
        protected void listContent_ItemCommand(object source, DataListCommandEventArgs e)
        {
            if (LinkClicked != null)
            {
                // Выделяем объект ссылки, на которой был выполнен щелчок
                LinkButton link = (LinkButton)e.Item.Controls[1];
    			
                // Выделяем нужные свойства Text и Url через атрибуты
                // дескриптора элемента LinkButton и сохраняем их
                // в экземпляре вспомогательного класса
                LinkTableItem item = new LinkTableItem(link.Text, link.CommandArgument);
    			
                // Передаем ссылку на сохраненные свойства в класс упаковки аргументов
                LinkTableEventArgs args = new LinkTableEventArgs(item);
    	
                // Генерируем событие
                LinkClicked(this, args);
    	
                // Пускаем клиента к закромам по ссылке, если класс-получатель не запретил
                if (args.Cancel != true)
                {
                    Response.Redirect(item.Url);
                }
            }
        }
    }
    	
    // Объявляем делегат события LinkClicked
    // в глобальной области видимости,
    // который определит сигнатуру события и обработчика
    public delegate void LinkClickedEventHandler(object sender, LinkTableEventArgs e);
    	
    // Класс-упаковка экземпляра LinkTableItem для 
    // передачи с событием в качестве аргумента
    public class LinkTableEventArgs : EventArgs
    {
        // Конструктор
        public LinkTableEventArgs(LinkTableItem item)
        {
            selectedItem = item;
        }
    	
        // Внутреннее поле будет хранить объект гиперссылки
        // и контролируется свойством "только для чтения"
        private LinkTableItem selectedItem;
        public LinkTableItem SelectedItem
        {
            get { return selectedItem; }
        }
    	
        // Внутренне поле хранит флаг отмены, переданный сгенерировавшим 
        // событие классом-источником для обработчика класса-приемника
        private bool cancel = false;// Инициализируется при создании
        public bool Cancel
        {
            get { return cancel; }
            set { cancel = value; }
        }
    }

    Теперь событие LinkClicked будет возникать всякий раз, когда пользователь щелкнет на гиперссылке пользовательского элемента управления. Классом-получателем этого события определим нашу тестовую страницу. Именно в ней мы создадим функцию-обработчик, которая распакует переданные событием аргументы и выведет полученную информацию на экран. Проверку существования события мы выполняем на всякий случай, чтобы наверняка предотвратить генерацию системного исключения.

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

  • Откройте на редактирование файл LinkTableExtTest.aspx.cs и в конце обработчика события Page_Load() класса LinkTableTest начните набирать имя объекта LinkTable1, под которым на тестовой странице у нас числится экземпляр зарегистрированного на ней пользовательского элемента управления. Вы сразу увидите реакцию подсказчика кода IntelliSense
  • Нажав два раза клавишу Tab мы получим и регистрацию события в классе-приемнике через глобального делегата, и заготовку обработчика. После генерации оболочкой таким способом заготовки обработчика далее можно поступить одним из следующих способов:

  • Либо оставить все как есть и начать заполнять обработчик
  • Либо оставить заготовку обработчика, а код регистрации события уничтожить
    LinkTable1.LinkClicked += new LinkClickedEventHandler(LinkTable1_LinkClicked);
  • Если выбрать второй способ, тогда нужно в дескрипторном представлении пользовательского элемента добавить атрибут OnLinkClicked="LinkTable1_LinkClicked", чтобы привязать к событию созданный обработчик (обратите внимание, что имя атрибута формируется из имени события и префикса On ). Но и в этом случае Большой Билл нам тоже чуть-чуть помогает

    Давайте выберем второй способ...

  • Уничтожте регистрацию события в классе LinkTableTest, скопируйте имя сгенерированного обработчика и добавьте его в атрибут OnLinkClicked="LinkTable1_LinkClicked" дескриптора <uc1:LinkTable ID="LinkTable1" runat="server" /> файла тестовой страницы LinkTableExtTest.aspx
  • В режиме Design тестовой страницы разместите текстовую метку asp:Label из вкладки Standard, задайте ей имя lblInfo и очистите поле ее свойства Text (либо удалите одноименный атрибут в дескрипторе). В ней мы будем отображать информацию о нажатой гиперссылке
  • Вернитесь в файл LinkTableExtTest.aspx.cs и заполните обработчик так
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTableTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Формируем заголовок списка
            LinkTable1.Title = "Список жизненно важных гиперссылок";
    	
            // Создаем список элементов - гиперссылок
            LinkTableItem[] items = new LinkTableItem[3];
            items[0] = new LinkTableItem("Ссылка №1 к Большому Биллу", 
                "http://www.microsoft.com");
            items[1] = new LinkTableItem("Ссылка №2 к автошколе \"Диалог-Сервис\"", 
                "http://www.dialog-service.net");
            items[2] = new LinkTableItem("Ссылка №3 к затычке JavaScript", 
                "javascript:void(0)");
            LinkTable1.Items = items;
        }
    	
        public void LinkTable1_LinkClicked(object sender, LinkTableEventArgs e)
        {
            // Ищем подстроку в вызывающей строке
            if (e.SelectedItem.Text.IndexOf("Диалог-Сервис") > 0)
            {
                e.Cancel = false;// Пропустить
            }
            else
            {
                e.Cancel = true; // Не пускаем дальше
                lblInfo.Text = "Вы щелкнули " +
                    "\"" + e.SelectedItem.Text + "\".";
                lblInfo.Text += " Извините, но Вам не разрешено идти на " +
                    e.SelectedItem.Url + "!!!";
            }
        }
    }
  • Запустите тестовую страницу LinkTableExtTest.aspx и полюбуйтесь на свою работу
  • Доступ из кода Web-страницы к составным компонентам пользовательского элемента управления

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

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

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

    В пользовательский элемент вводят промежуточные свойства или методы доступа, через которые из Web-страницы обращаются к его компонентам. Это напоминает прием, который применяется в C++ для поддержания инкапсуляции (закрытости) данных: переменные-члены класса с модификатором доступа private упаковывают в функции доступа public, которые сами видят эти переменные, а их, в свою очередь, видят клиенты этого класса.

    Пусть, например, пользовательский элемент управления MyControl1 как объект порождается классом MyControl и в качестве одного из компонентов содержит в себе объект Label1 библиотечного класса System.Web.UI.WebControls.Label. Класс Label наследует от базового класса System.Web.UI.WebControls.WebControl свойство BackColor, которое, в свою очередь, имеет тип System.Drawing.Color. Напрямую из Web-страницы нельзя установить новое значение свойства BackColor компонента Label1 пользовательского элемента. Но можно раскрыть это свойство для Web-страницы, введя промежуточное свойство BackColor (или с другим именем) в классе самого пользовательского элемента. Сказанное схематично изображено на рисунке

    Выполним это на практике.

  • Создайте заготовку пользовательского элемента Web User Control с именем MyControl.ascx с раздельным кодом (флажок Place code in separate file мастера включен)
  • Перейдите в режим Design редактора заготовки и поместите на нее из вкладки Standard элемент Label с именем Label1
  • В панели Solution Explorer откройте файл поддержки MyControl.ascx.cs, удалите из класса MyControl заготовку обработчика Page_Load() за ненадобностью и дополните класс свойствами доступа, чтобы он стал таким
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControl : System.Web.UI.UserControl
    {
        public string Text
        {
            get { return Label1.Text; }
            set { Label1.Text = value; }
        }
    	
        public System.Drawing.Color BackColor
        {
            get { return Label1.BackColor; }
            set { Label1.BackColor = value; }
        }
    	
        public System.Drawing.Color ForeColor
        {
            get { return Label1.ForeColor; }
            set { Label1.ForeColor = value; }
        }
    }

    Здесь мы использовали имена посреднических свойств родителя MyControl1 точно такие, как у прямых свойств компонента Label1. Но это не обязательно. Можно использовать любые имена. Просто, когда Иванов, Петров, Сидоров означают одно и то же лицо, но в разных инстанция числятся по разному, это " хужее ".

  • Создайте пользовательскую тестовую страницу с раздельным кодом под именем MyControlTest.aspx и переведите ее в режим редактирования Design
  • Из панели Solution Explorer из дерева файлов приложения перенесите на тестовую страницу узел MyControl.ascx. Убедитесь, что экземпляр пользовательского элемента управления зарегистрировался на тестовой странице под именем MyControl1
  • Попробуйте выделить элемент управления и убедитесь, что он не выделяется и добавленные нами свойства не видны в панели Properties. Это значит, что декларативно (в режиме проектирования) из страницы настраивать его нелься, но можно программно управлять (в режиме выполнения) через введенные нами общедоступные свойства, в чем мы и хотим убедиться.
  • Поместите на страницу под пользовательским элементом управления компонент RadioButtonList из вкладки Standard и убедитесь, что он имеет ID="RadioButtonList1"
  • Через коллекцию Items элемента RadioButtonList1 задайте три радиокнопки с такими свойствами
  • Text="Красный"; Value="Red"
  • Text="Зеленый"; Value="Green"
  • Text="Синий"; Value="Blue"
  • Установите свойство RepeatDirection элемента RadioButtonList1 в значение Horozontal
  • Выделите компонент RadioButtonList1 и через панель Properties создайте обработчик для его события SelectedIndexChanged
  • Под списком радиокнопок поместите компонент Input (Submit) из вкладки HTML
  • Дескрипторное представление страницы будет иметь вид

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="MyControlTest.aspx.cs" 
        Inherits="MyControlTest" %>
    	
    <%@ Register Src="MyControl.ascx" TagName="MyControl" TagPrefix="uc1" %>
    
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <uc1:MyControl ID="MyControl1" runat="server" />
            <br />
            <br />
            <asp:RadioButtonList ID="RadioButtonList1" runat="server" 
                 RepeatDirection="Horizontal" 
                 OnSelectedIndexChanged="RadioButtonList1_SelectedIndexChanged">
                <asp:ListItem Value="Red">Красный</asp:ListItem>
                <asp:ListItem Value="Green">Зеленый</asp:ListItem>
                <asp:ListItem Value="Blue">Синий</asp:ListItem>
            </asp:RadioButtonList></div>
            <br />
            <input id="Submit1" type="submit" value="submit" />
        </form>
    </body>
    </html>

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

  • Заполните обработчик RadioButtonList1_SelectedIndexChanged() страницы в файле MyControlTest.aspx.cs следующим кодом
    (рис ) Код обработчика в файле MyControlTest.aspx.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControlTest : System.Web.UI.Page
    {
        protected void RadioButtonList1_SelectedIndexChanged(object sender, EventArgs e)
        {
            int index = RadioButtonList1.SelectedIndex;
    	
            switch (index)
            {
                case 0:
                    MyControl1.ForeColor = System.Drawing.Color.White;
                    MyControl1.BackColor = System.Drawing.Color.Red;
                    break;
                case 1:
                    MyControl1.ForeColor = System.Drawing.Color.Aqua;
                    MyControl1.BackColor = System.Drawing.Color.Green;
                    break;
                case 2:
                    MyControl1.ForeColor = System.Drawing.Color.Yellow;
                    MyControl1.BackColor = System.Drawing.Color.Blue;
                    break;
            }
    	
            if(index != -1)
                MyControl1.Text = RadioButtonList1.Items[index].Value;
        }
    }
  • Откомпилируйте страницу и проверьте ее работоспособность. При щелчке на кнопке код страницы меняет свойства компонента Label1, хотя он входит в состав пользовательского элемента управления MyControl1. Взаимодействие страницы с Label1 происходит через добавленные нами в пользовательский элемент управления дополнительные глобальные свойства.
  • Второй способ

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

    Поясним сказанное на предыдущем примере.

  • Скопируйте через панель Solution Explorer, как это мы делали ранее, файл тестовой страницы MyControlTest.aspx и файл пользовательского элемента управления MyControl.ascx
  • Присвойте копиям новые имена MyControlExtTest.aspx и MyControlExt.ascx соответственно (файлы сопровождающего скрытого кода оболочка переименует автоматически)
  • Сделайте страницу MyControlExtTest.aspx стартовой, выполнив на ее узле в панели Solution Explorer команду Set As Start Page
  • Измените параметр Src директивы регистрации в файле MyControlExtTest.aspx
    <%@ Register Src="MyControlExt.ascx" TagName="MyControl" TagPrefix="uc1" %>
  • Откройте файл скрытого кода MyControlExt.ascx.cs и измените в нем класс MyControl, чтобы он выглядел так
    (рис ) Добавление в класс пользовательского элемента управления свойства-ссылки на его внутренний компонент Label1 в файле MyControlExt.ascx.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControlExt : System.Web.UI.UserControl
    {
        // Раскрываем весь компонент Label1
        // Имя свойства InnerLabel1 выбрали произвольно
        // Используется только get-аксессор
        public Label InnerLabel1
        {
            get { return Label1; }
        }
    }
  • Скорректируйте в файле MyControlExt.ascx атрибут директивы Control так Inherits="MyControlExt"
  • Откройте файл MyControlExtTest.aspx.cs скрытого кода тестовой страницы и отредактируйте его для доступа к прямым свойствам компонента Label1 с использованием созданного адреса InnerLabel1
    (рис ) Доступ к прямым свойствам Label1 через адрес InnerLabel1 на странице MyControlExtTest.aspxusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControlExtTest : System.Web.UI.Page
    {
        protected void RadioButtonList1_SelectedIndexChanged(object sender, EventArgs e)
        {
            int index = RadioButtonList1.SelectedIndex;
    
            // Доступ к прямым свойствам компонента Label1 выполняем
            // через ссылку на этот компонент в родителе MyControl1
            switch (index)
            {
                case 0:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.White;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Red;
                    break;
                case 1:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.Aqua;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Green;
                    break;
                case 2:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.Yellow;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Blue;
                    break;
            }
    
            if(index != -1)
                MyControl1.InnerLabel1.Text = RadioButtonList1.Items[index].Value;
        }
    }
  • Выполните страницу и убедитесь, что получился тот же самый результат, что и в предыдущем примере при реализации первого способа
  • Какой способ выбрать - решать Вам.

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

    На протяжении этой темы мы рассматривали вопрос создания пользовательского элемента управления в режиме проектирования и последующее управление им в программном режиме. Но может потребоваться на странице подгружать и настраивать пользовательский элемент управления динамически ("на лету"). Мы уже программно создавали из библиотечного элемента управления HtmlTable объект-таблицу на странице DynamicTable.aspx. Теперь посмотрим, как это делается для пользовательского элемента управления.

    Требуется выполнить следующие шаги:

  • Создавать при каждой загрузке страницы в обработчике ее события Page.Load экземпляр класса пользовательского элемента управления, заранее подготовленного в файлах *.ascx и *.ascx.cs. Создание и регистрация экземпляра пользовательского элемента в коде страницы осуществляется статическим методом Page.LoadControl().
  • Резервировать с помощью элемента PlaceHolder место размещения для точного позиционирования на странице пользовательского элемента управления.
  • Обязательно присваивать после динамического создания значение свойства ID пользовательского элемента управления, особенно, когда создается несколько экземпляров одного и того же элемента. Это может понадобиться для однозначной идентификации элемента в коде страницы при поиске ссылки на экземпляр, выполняемом с помощью статического метода Page.FindControl(), который как раз использует значение идентификатора ID.
  • Есть некоторая специфика динамического создания пользовательского элемента управления. Нельзя создать пользовательский элемент напрямую, как это делается с библиотечным элементом управления через конструктор класса. Пользовательский элемент управления состоит не только из поддерживающего класса в файле *.ascx.cs, но и дескрипторной части файла *.ascx. Поэтому на этапе проектирования он не представлен общим классом, экземпляр которого можно было-бы создать с помощью конструктора. Экземпляр пользовательского элемента управления создается самой ASP.NET объединением этих двух частей на этапе выполнения.

  • Скопируйте через панель Solution Explorer, как это мы делали ранее, файл тестовой страницы MyControlExtTest.aspx и файл пользовательского элемента управления MyControlExt.ascx
  • Присвойте копиям новые имена MyControlDynamicTest.aspx и MyControlDynamic.ascx соответственно (файлы сопровождающего скрытого кода оболочка переименует автоматически)
  • Сделайте страницу MyControlDynamicTest.aspx стартовой, выполнив на ее узле в панели Solution Explorer команду Set As Start Page
  • Закройте все предыдущие редактируемые документы командой меню Window/Close All Documents
  • Откройте на редактирование страницу MyControlDynamicTest.aspx в режиме Design, удалите наш пользовательский элемент управления Label1 и поместите на его место из вкладки Standard стандартный элемент управления PlaceHolder
  • Перейдите в файле MyControlDynamicTest.aspx в режим Source редактора страницы и удалите полностью старый код директивы регистрации
    <%@ Register Src="MyControlExt.ascx" TagName="MyControl" TagPrefix="uc1" %>
  • На данном этапе страница в дескрипторном представлении будет такой

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="MyControlDynamicTest.aspx.cs" 
        Inherits="MyControlTest" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:PlaceHolder ID="PlaceHolder1" runat="server" />
            <br />
            <br />
            <asp:RadioButtonList ID="RadioButtonList1" runat="server" 
                                 RepeatDirection="Horizontal">
                <asp:ListItem Value="Red">Красный</asp:ListItem>
                <asp:ListItem Value="Green">Зеленый</asp:ListItem>
                <asp:ListItem Value="Blue">Синий</asp:ListItem>
            </asp:RadioButtonList>
            <br />
            <input id="Submit1" type="submit" value="submit" />
        </div>
        </form>
    </body>
    </html>

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

  • Откройте на редактирование файл скрытого кода
  • Измените код обработчика Page_Load(), чтобы он стал таким
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControlTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Создаем экземпляр класса поддержки
            // пользовательского элемента управления
            MyControl MyControl1 = (MyControl)Page.LoadControl("MyControlDynamic.ascx");
            // Добавляем объект в зарезервированное место страницы
            PlaceHolder1.Controls.Add(MyControl1);
    	
            int index = RadioButtonList1.SelectedIndex;
    	
            // Доступ к прямым свойствам компонента Label1 выполняем
            // через ссылку на этот компонент в родителе MyControl1
            switch (index)
            {
                case 0:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.White;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Red;
                    break;
                case 1:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.Aqua;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Green;
                    break;
                case 2:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.Yellow;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Blue;
                    break;
            }
    	
            if(index != -1)
                MyControl1.InnerLabel1.Text = RadioButtonList1.Items[index].Value;
        }
    }

    Обратите внимание, что при наборе кода создания пользовательского элемента в редакторе кода механизм IntelliSense нам не помогает, потому что страница не знает, что где-то в файле MyControlDynamic.ascx существует класс MyControl.

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

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

  • Сначала нужно разработать обычную страницу, отладить ее и проверить в действии.
  • Переименовать расширение файла из aspx в ascx
  • Директиву @Page вместе с ее атрибутами следует удалить и заменить директивой @Control с требуемыми атрибутами.
  • Удалить из кода все дескрипторы <html>, <body>, <form>, <head>.
  • В коде файла поддержки заменить базовый класс Page на UserControl.
  • Если мы используем модель совмещенного кода, то нужно в директиву Control обязательно добавить атрибут ClassName="имя_файла_или_любое_имя". Он извещает ASP.NET, что код, который нужно компилировать, находится в том же файле в дескрипторах <script>. Без этой информации файл компилироваться не будет.
  • Управление динамической загрузкой многих пользовательских элементов управления (портальные каркасы)

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

  • Header.ascx - верхний колонтитул
  • TimeDisplay.ascx - кнопка-ссылка, возвращающая неформатированную и форматированную дату-время
  • LinkTable.ascx - список гиперссылок
  • LinkTableExt.ascx - список гиперссылок с предварительным анализом выбора пользователя (для этого создали свое событие)
  • MyControl.ascx - с применением промежуточных средств доступа к настройкам внутренних компонентов
  • MyControlExt.ascx - с созданием общего свойства-ссылки на встроенный компонент для доступа к любым его членам
  • MyControlDynamic.ascx - то же самое, что и MyControlExt.ascx, только применяли способ динамической загрузки пользовательского элемента на тестовую страницу
  • Для каждого из этих элементов управления разрабатывалась своя тестовая страница, на которую элемент управления помещался декларативно. Только в последнем случае мы сам пользовательский элемент не изменяли, но на тестовой странице создавали его динамически (программно). Теперь давайте на примере рассмотрим вопрос, когда на страницу нужно поместить программно сразу несколько разных пользовательских элементов управления.

  • Создайте новую страницу DynamicUserControls.aspx с раздельным кодом и сделайте ее стартовой
  • В раскрывающемся списке панели Properties выберите элемент DOCUMENT и задайте его свойству Title значение Портальные каркасы
  • В режиме Design поместите с вкладки Standard элемент Panel и присвойте ему имя Panel1 (оно генерируется автоматически). Этот элемент будет служить контейнером для других элементов управления, в том числе и для пользовательского
  • Поместите внутрь контейнера Panel1 друг за другом по вертикали стандартные элементы
  • DropDownList с автоматически сгенерированным именем DropDownList1 - для выбора загружаемого в контейнер пользовательского элемента управления
  • PlaceHolder с автоматически сгенерированным именем PlaceHolder1 - для позиционирования в контейнере пользовательского элемента управления
  • Label с автоматически сгенерированным именем Label1 - для отображения имени файла, из которого в контейнер загружен пользовательский элемент управления
  • По большому счету нам в данном примере безразлично, какие имена будут иметь экземпляры дочерних элементов. Однозначно различать их в коде мы можем по найденным ссылкам. Ссылки, в свою очередь, будем искать в именованном контейнере по типу дочернего элемента, поскольку помещенные нами элементы имеют разный тип. Но сама оболочка Visual Studio 2005 требует, чтобы на странице не было декларативно объявляемых дескрипторов с одинаковыми значениями ID.

  • Проследите в дескрипторном представлении страницы, чтобы ни один из элементов, включая контейнер, не имел атрибутов Width и Height, ограничивающих свободную расширяемость дочерних элементов
  • Заметим, что контейнер Panel1, как родитель, по умолчанию будет определять основные свойства своих дочерних элементов, такие как цвет фона, цвет переднего плана, шрифт и т.д., если только мы не определим их явным образом.

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

  • Header.ascx - Верхний колонтитул
  • TimeDisplay.ascx - Кнопка 'время-дата'
  • LinkTable.ascx - Список гиперссылок
  • LinkTableExt.ascx - Контролируемый список гиперссылок
  • MyControl.ascx - С настройкой внутренних компонентов
  • Выделите объект DropDownList1 в контейнере Place1 и заполните декларативно его свойство Items так
    Декларативные настройки объекта DropDownList1
    Items[i] Text Value
    Items[0] (None) None
    Items[1] Верхний колонтитул HeaderTest.ascx
    Items[2] Кнопка 'время-дата' TimeDisplayTest.ascx
    Items[3] Список гиперссылок LinkTableTest.ascx
    Items[4] Контролируемый список гиперссылок LinkTableExtTest.ascx
    Items[5] С настройкой внутренних компонентов MyControlTest.ascx
  • В скрытом поле Value мы указали имена файлов, которые в будущем должны будем создать
  • Установите для элемента DropDownList1 его свойство AutoPostBack в значение True, чтобы обеспечить постинг страницы при выборе пользователем нужной опции
  • Будем считать, что один из трех стандартных элементов управления контейнера, а именно список, мы заполнили декларативно. Два других элемента контейнера будем заполнять программно.

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

  • В режиме Design тестовой страницы разместите по вертикали друг за другом три копии контейнера Place1 вместе с его дочерними элементами. Проследите, чтобы контейнеры имели имена Panel1, Panel2, Panel3
  • Через раскрывающийся список панели Properties выделяйте последовательно каждый контейнер Panel1, Panel2, Panel3 и настройте их свойства так
  • BackColor=Web/Gainsboro
  • BorderWidth=1px
  • HorizontalAlign=Center
  • BorderStyle=Ridge
  • EnableViewState="False"
  • Мы закончили этап проектирования страницы для данного примера. Дескрипторное представление страницы должно быть таким

    <%@ Page AutoEventWireup="true" CodeFile="DynamicUserControls.aspx.cs" Inherits="DynamicUserControls"
        Language="C#" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Портальные каркасы</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Panel ID="Panel1" runat="server" BackColor="Gainsboro" BorderStyle="Ridge" BorderWidth="1px"
                    HorizontalAlign="Center" EnableViewState="False">
                    <asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True">
                        <asp:ListItem Value="None">(None)</asp:ListItem>
                        <asp:ListItem Value="HeaderTest.ascx">Верхний колонтитул</asp:ListItem>
                        <asp:ListItem Value="TimeDisplayTest.ascx">Кнопка 'время-дата'</asp:ListItem>
                        <asp:ListItem Value="LinkTableTest.ascx">Список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="LinkTableExtTest.ascx">Контролируемый список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="MyControlTest.ascx">С настройкой внутренних компонентов</asp:ListItem>
                    </asp:DropDownList><br />
                    <br />
                    <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
                    <br />
                    <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label></asp:Panel>
                <br />
                <asp:Panel ID="Panel2" runat="server" BackColor="Gainsboro" BorderStyle="Ridge" BorderWidth="1px"
                    EnableViewState="False" HorizontalAlign="Center">
                    <asp:DropDownList ID="DropDownList2" runat="server" AutoPostBack="True">
                        <asp:ListItem Value="None">(None)</asp:ListItem>
                        <asp:ListItem Value="HeaderTest.ascx">Верхний колонтитул</asp:ListItem>
                        <asp:ListItem Value="TimeDisplayTest.ascx">Кнопка 'время-дата'</asp:ListItem>
                        <asp:ListItem Value="LinkTableTest.ascx">Список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="LinkTableExtTest.ascx">Контролируемый список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="MyControlTest.ascx">С настройкой внутренних компонентов</asp:ListItem>
                    </asp:DropDownList><br />
                    <br />
                    <asp:PlaceHolder ID="PlaceHolder2" runat="server"></asp:PlaceHolder>
                    <br />
                    <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label></asp:Panel>
                <br />
                <asp:Panel ID="Panel3" runat="server" BackColor="Gainsboro" BorderStyle="Ridge" BorderWidth="1px"
                    EnableViewState="False" HorizontalAlign="Center">
                    <asp:DropDownList ID="DropDownList3" runat="server" AutoPostBack="True">
                        <asp:ListItem Value="None">(None)</asp:ListItem>
                        <asp:ListItem Value="HeaderTest.ascx">Верхний колонтитул</asp:ListItem>
                        <asp:ListItem Value="TimeDisplayTest.ascx">Кнопка 'время-дата'</asp:ListItem>
                        <asp:ListItem Value="LinkTableTest.ascx">Список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="LinkTableExtTest.ascx">Контролируемый список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="MyControlTest.ascx">С настройкой внутренних компонентов</asp:ListItem>
                    </asp:DropDownList><br />
                    <br />
                    <asp:PlaceHolder ID="PlaceHolder3" runat="server"></asp:PlaceHolder>
                    <br />
                    <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label></asp:Panel>
                <br />
            </div>
        </form>
    </body>
    </html>
  • Выполните сформированную страницу DynamicUserControls.aspx, чтобы убедиться в работоспособности настроек режима проектирования. Результат на данном этапе должен быть таким
  • Таким образом, мы выполнили интерфейсную заготовку портальных каркасов на тестовой странице. Теперь пришла пора наполнить их функциональностью, которая бы обеспечила контейнерную загрузку выбранных пользовательских элементов управления. Эту функциональность мы обеспечим, кодируя файл DynamicUserControls.aspx.cs скрытого кода страницы. Ниже мы убедимся, что программно загруженные в портальные каркасы пользовательские элементы управления обрабатывают свои события точно также, как будто бы каждый из них находится на отдельной тестовой странице.

  • Откройте на редактирование файл DynamicUserControls.aspx.cs
  • Добавьте в класс DynamicUserControls следующий код
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class DynamicUserControls : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Загружаем портальные каркасы
            MyLoadControls(Panel1);
            MyLoadControls(Panel2);
            MyLoadControls(Panel3);
        }
    	
        // Вспомогательная функция
        private void MyLoadControls(Control containerPanel)
        {
            // Объявляем вспомогательные ссылки на интерфейсные
            // элементы текущего контейнера
            DropDownList list = null;
            PlaceHolder ph = null;
            Label lbl = null;
    	
            // Перебираем все дочерние элементы текущего 
            // контейнера и ищем ссылки на объекты дочерних
            // интерфейсных элементов по их типам
            foreach (Control ctrl in containerPanel.Controls)
            {
                if (ctrl is DropDownList)
                    list = (DropDownList)ctrl;
                else if (ctrl is PlaceHolder)
                    ph = (PlaceHolder)ctrl;
                else if (ctrl is Label)
                    lbl = (Label)ctrl;
            }
    	
            // Берем информацию из выделенного
            // элемента списка текущего контейнера
            string lstValue = list.SelectedItem.Value;
            string lstText = list.SelectedItem.Text;
            lbl.Text = "";
    	
            // Заполняем текущий контейнер.
            // Проверяем наличие нужного расширения
            if (lstValue.EndsWith(".ascx"))
            {
                ph.Controls.Add(Page.LoadControl(lstValue));
                lbl.Text = "Загрузили: " + lstValue + " - " + lstText;
            }
        }
    }

    Создадим пользовательские элементы управления, которые реализуют пользовательский интерфейс и функциональность, использованную нами при выполнении примеров в тестовых страницах. Из этих тестовых страниц мы их и переделаем.

  • В панели Solution Explore выделите при нажатой клавише Ctrl следующие файлы
  • HeaderTest.aspx
  • TimeDisplayTest.aspx
  • LinkTableTest.aspx
  • LinkTableExtTest.aspx
  • MyControlTest.aspx
  • Выполните команду Copy затем Paste (на верхнем узле проекта) контекстного меню
  • Присвойте копиям те же самые имена, только с расширением .ascx
  • HeaderTest.ascx
  • TimeDisplayTest.ascx
  • LinkTableTest.ascx
  • LinkTableExtTest.ascx
  • MyControlTest.ascx
  • Откройте новые файлы в дескрипторном режиме и замените директиву @Page на @Control, все остальные атрибуты директивы @Page оставьте прежними. Теперь это будут не страницы, а пользовательские элементы управления. Директиву @Register с ее атрибутами оставьте в покое
  • Удалить из кода все дескрипторы <html>, <body>, <form>, <head> и закрывающие их дескрипторы.
  • Для новых файлов *.ascx откройте файлы скрытого кода *.ascx.cs и замените в объявлении класса имя наследующего класса с " : System.Web.UI.Page " на " : System.Web.UI.UserControl ". Остальной код классов останется прежним.
  • Мы закончили выполнение примера. Код тестовой страницы при очередной загрузке будет программно создавать свои пользовательские элементы управления для каждого контейнера Panel и размещать их в элементах PlaceHolder.

  • Выполните страницу DynamicUserControls.aspx и испытайте ее. Результат будет примерно таким
  • Иногда при отладке приложения оболочка начинает сбоить, выдавая непонятные ошибки. Может помочь перезапуск Visual Studio 2005 или удаление файла web.config, все равно оболочка создаст его автоматически при очередной компиляции. Пока нам этот файл не жалко, потому что его настройками мы все-равно еще не занимались.
    Страницы:
    Файлы к лекции Вы можете скачать здесь

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

    Пользовательский элемент управления представляет собой небольшую часть страницы, которая может содержать статический HTML-код и стандартные элементы управления. В пользовательских элементах управления можно предусмотреть свои свойства, методы и события. Пользовательские блоки кода могут многократно использоваться на разных страницах, но только одного сайта. Для применения в другом сайте его либо нужно скопировать в этот сайт, либо создавать библиотечные специальные серверные элементы наподобие стандартных ( custom server control ).

    Файл пользовательского элемента управления может содержать то же самое, что и файл обычной исполнимой страницы, включая файл поддержки .cs, статический HTML-код, стандартные элементы управления. Они получают те же самые события, что и объект Page, имеют доступ к таким важным объектам-свойствам, как Application, Session, Request, Response. Но есть и существенные отличия:

  • В отличие от страниц, файлы которых имеют расширение .aspx, пользовательские элементы управления должны храниться в файлах с расширением .ascx
  • Объект страницы произведен от класса
  • Среда исполнения не может напрямую вызвать файл .ascx, а должна это делать через .aspx, в который внедряется ссылка на файл .ascx
  • Файл пользовательского элемента управления не является отдельной страницей, поэтому не должен содержать дескрипторы <html>, <head>, <body>, <form>
  • Вместо директивы @Page файл пользовательского элемента управления содержит директиву @Control
  • Среда проектирования позволяет видеть внедренный в страницу пользовательский элемент управления на поверхности конструктора Design страницы.

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

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

  • Выполните команду Website/Add New Item, выберите шаблон Web User Control и задайте имя Header.ascx
  • Посмотрите, что создал мастер. Файл .ascx содержит только одну строку директивы

    <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Header.ascx.cs" Inherits="Header" %>

    Пользовательский элемент управления редактируется в оболочке точно также, как и обычная страница. Файл поддержки .ascx.cs содержит тот же код, что и для обычной страницы. В данном примере он нам не нужен, поэтому ничего в нем делать не будем (пусть болтается пустой).

  • Поместите в файл Header.ascx следующую HTML-разметку
  • <table width="100%"  border="0" bgcolor="blue">
      <tr>
        <td>
    	<font face="Vernada, Arial" size="6" color="yellow">
    	   <b>Тестовая страница User Control</b>
    	</font>	</td>
      </tr>
      <tr>
        <td align="right">
    	  <font size="3" color="white">
    	    <b>Время создания: март 2007г. copy; Все права защищены</b>
        </font>	</td>
      </tr>
    </table>

    Эта разметка даст такое визуальное представление

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

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

  • Создайте новую страницу с именем HeaderTest.aspx для внедрения в нее пользовательского элемента управления
  • Чтобы использовать элемент управления на странице, нужно интегрировать его в форму. Для этого нужно выполнить следующие шаги:

  • Зарегистрировать элемент на странице, т.е. с помощью директивы @Register, помещенной в начале страницы, нужно сообщить ASP.NET, что где-то на странице есть ссылка на пользовательский элемент управления.
  • Поместить пользовательский элемент управления на страницу, вставив в нужное место внутри дескриптора <form> ссылку-тег на этот элемент.
  • Директива @Register для нашего примера имеет следующий вид

    <%@ Register TagPrefix="myElem" TagName="Header" Src="Header.ascx" %>

    Мы знаем, что для различения стандартных элементов управления в начале их дескрипторного представления используется конструкция <префикс:тип_элемента атрибуты... />, например

    <asp:TextBox ID="TextBox1" runat="server" />

    Здесь asp играет роль префикса TagPrefix (или условного пространства имен), а TextBox - тип элемента управления TagName. Директива регистрации также для лучшего различения пользовательских элементов управления поддерживает одинаковый синтаксис. Значения myElem и Header мы задали произвольно, но далее эти значения на странице мы должны строго соблюдать, а значение атрибута Src ( Source - источник) означает подключаемый файл с пользовательским элементом управления.

    В зарегистрированных обозначениях пользовательского элемента управления синтаксис его подключения к странице HeaderTest.aspx будет таким (ну, как обычно!):

    <myElem:Header ID="Header1" runat="server" ></myElem:Header>

    или таким

    <myElem:Header ID="Header1" runat="server" />
    В Visual Studio 2005 нам не нужно руками прописывать приведенную регистрацию. Достаточно в окне Solution Explorer перенести внедряемый файл Header.ascx в нужное место страницы и подправить, при необходимости, сгенерированную оболочкой регистрацию.

    В результате пользовательская страница HeaderTest.aspx должна иметь такую HTML-разметку

    <%@ Page AutoEventWireup="true" CodeFile="HeaderTest.aspx.cs" Inherits="HeaderTest"
        Language="C#" %>
    <%@ Register TagPrefix="myElem" TagName="Header" Src="Header.ascx" %>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Тест элемента user control</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <myElem:Header ID="Header1" runat="server" />
            </div>
        </form>
    </body>
    </html>

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

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

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

    Обработка событий

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

  • Вызовите мастер для создания пользовательского элемента управления, выполнив команду Website/Add New Item. Установите шаблон в значение Web User Control, сбросьте флажок Place code in separate file и задайте имя файла TimeDisplay.ascx
  • Поместите в редактор Design стандартный элемент управления LinkButton из вкладки Standard
  • Через панель Properties измените ID элемента на lnkTime
  • Двойным щелчком на свободном месте редактора Design создайте обработчик Page_Load()
  • При выделенном элементе lnkTime установите панель Properties в режим Events (кнопка с изображением молнии вверху панели), найдите событие Click и двойным щелчком на поле его значения создайте обработчик lnkTime_Click()
  • Заполните файл следующим кодом
  • <%@ Control Language="C#" ClassName="TimeDisplay" %>
    	
    <script runat="server">
    	
        protected void Page_Load(object sender, EventArgs e)
        {
            RefreshTime();
        }
    	
        protected void lnkTime_Click(object sender, EventArgs e)
        {
            RefreshTime();
        }
    	
        public void RefreshTime()
        {
            lnkTime.Text = DateTime.Now.ToLongTimeString();
        }
    </script>
    
    <asp:LinkButton ID="lnkTime" runat="server" OnClick="lnkTime_Click" />

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

  • Чтобы протестировать наш код, добавьте к приложению новую страницу с именем TimeDisplayTest.aspx
  • Перетащите на нее из панели Solution Explorer файл TimeDisplay.ascx с пользовательским элементом управления
  • Выполните страницу, которая на клиенте будет такой
  • При каждом щелчке пользователя на ссылке-кнопке она будет снимать (и отображать) с таймера сервера новое значение времени.

    Понятие свойства класса

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

  • типа, равного типу поля, доступом к которому оно управляет
  • из имени с модификатором доступа, по которому будут читаться или писаться значения поля, контролируемые свойством
  • из пары аксессоров get и set, содержащих контролирующий поле код
  • Основное достоинство свойства состоит в том, что его можно использовать в выражениях и инструкциях присваивания как обычную переменную-член класса, хотя на самом деле здесь автоматически будут вызываться get - и set -аксессоры. Синтакис свойства таков:

    модификатор_доступа тип имя_свойства
    	{ 
    		get
    		{
    			// код аксессора чтения поля
    			............................
    			return поле;
    		}
    		set
    		{
    			// код аксессора записи поля
    			............................
    			поле = value;
    		}
    	}

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

    class MyClass
    {
    	public MyClass()
    	{
    		xField = 0;
    	}
    	
    	private int xField; // Это поле будет управляться свойством x
    
    	// Это свойство поддерживает доступ к закрытому полю xField
    	// через имя доступа x, равное имени свойства
    	// Для клиента этого класса x видна как переменная
    	public int x
    	{
    		get
    		{
    			return xField;
    		}
    		set
    		{
    			// Переменная value - зарезервированное имя переменной
    			// обобщенного типа, принимающей значение от клиента
    			// Это имя менять нельзя
    			if(value >= 0)
    				xField = value;
    		}
    	}
    }

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

    Добавление свойств

    Вернемся к нашей теме разработки пользовательского элемента управления. Расширим возможности предыдущего примера. Добавим в код общедоступное свойство Format, которому в клиенте (использующая элемент страница) будем передавать либо пустую строку, либо строку форматирования. В зависимости от этого пользовательский элемент управления будет способен отображать надпись на кнопке LinkButton в разных форматах.

  • Измените код файла TimeDisplay.ascx следующим образом
    (рис ) Улучшенный код файла TimeDisplay.ascx<%@ Control Language="C#" ClassName="xx" %>
    	
    <script runat="server">
        
        protected void Page_Load(object sender, EventArgs e)
        {
            RefreshTime();
        }
    	
        private string format; // Контролируемое свойством поле
        public string Format   // Определение свойства Format
        {
            get { return format; }
            set { format = value; }
        }
    	
        protected void lnkTime_Click(object sender, EventArgs e)
        {
            RefreshTime();
        }
    	
        public void RefreshTime()
        {
            if (format == "")
                lnkTime.Text = DateTime.Now.ToLongTimeString();
            else // Значение format задаем в клиенте через свойство Format
                lnkTime.Text = DateTime.Now.ToString(format);
        }
    </script>
    
    <asp:LinkButton ID="lnkTime" runat="server" OnClick="lnkTime_Click" />
  • Измените код тестовой страницы TimeDisplayTest.aspx следующим образом
    (рис ) Код тестовой страницы TimeDisplayTest.aspx<%@ Page Language="C#" %>
    	
    <%@ Register Src="TimeDisplay.ascx" TagName="TimeDisplay" TagPrefix="uc1" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Страница TimeDisplayTest</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <uc1:TimeDisplay ID="TimeDisplay1"
                    runat="server"
                    Format="" />
                <br />
                <br />
                <hr />
                <br />
                <uc1:TimeDisplay ID="TimeDisplay2" 
                    runat="server"
                    Format="Сегодня dddd, dd MMMM yyyy<br />Текущее время HH:mm:ss" />
            </div>
        </form>
    </body>
    </html>
  • Тестовая страница является клиентом пользовательского элемента управления. Именно в дескрипторе элемента на клиенте мы определяем значение добавленного свойства Format как атрибут этого дескриптора. Обратите внимание, что в подсказчике кода IntelliSense, применяемом при редактировании дескриптора пользовательского элемента управления, появилось новое свойство Format.

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

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

    Порядок генерации событий инициализации

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

  • Производится удаленный запрос страницы и она загружается в память сервера
  • Создается и инициализируется конструктором класса пользовательский элемент управления
  • Присваиваются значения атрибутов, предусмотренные в дескрипторе пользовательского элемента управления
  • Создается объект родительской страницы
  • Выполняется событие Page.Load родительской страницы, в обработчике Page_Load() которого можно предусмотреть переназначение атрибутов-свойств пользовательского элемента управления
  • Выполняется событие Page.Load пользовательского элемента управления
  • Отсюда важный вывод, что если мы будем инициализировать пользовательский элемент управления в его обработчике Page_Load(), то он перезапишет все свои настройки, выполненные в родительском коде, поскольку срабатывает последним. Так, если бы в последнем примере обработчик Page_Load() пользовательского элемента управления инициализировал поле format и имел бы вид

    protected void Page_Load(object sender, EventArgs e)
    {
        format = "";
        if (!Page.IsPostBack)
            RefreshTime();
    }

    то мы бы всегда получали результат варианта без форматирования, а именно

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

    Иногда приходится создавать специальные классы для организации взаимодействия между пользовательским элементом управления и Web-страницей. Рассмотрим пример, в котором создадим сложный пользовательский элемент управления LinkTable, предназначенный для генерирования набора гиперссылок в форматированной таблице. Для удобства часть кода разместим в отдельном самостоятельном классе LinkTableItem.

  • Создайте страницу Web Form с раздельным кодом и именем LinkTableTest.aspx
  • Сделайте эту страницу стартовой
  • Добавьте к проекту новую папку с зарезервированным именем
  • Через контекстное меню на созданной папке выполните команду
  • Через окно мастера добавьте к приложению новый класс C# с именем Этот класс в своих полях будет хранить информацию, необходимую пользовательскому элементу управления, а через свойства осуществлять доступ к ней.
  • Заполните вспомогательный класс в файле LinkTableItem.cs следующим кодом
    (рис ) Код файла App_Code/LinkTableItem.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    /// <summary>
    /// Вспомогательный класс LinkTableItem
    /// </summary>
    public class LinkTableItem
    {
        // Хранимые поля закрытого типа
        private string text, url;
    	
        // Конструктор по умолчанию
    	public LinkTableItem()
    	{
            // Пустой конструктор по умолчанию обязателен
            // поскольку есть общий конструктор
    	}
    	
        // Общий конструктор
        public LinkTableItem(string text, string url)
        {
            // Внутренним полям присваиваются значения,
            // переданные из родительского объекта
            this.text = text;
            this.url = url;
        }
    	
        // Определение свойств доступа к внутренним полям
        public string Text
        {
            get { return text; }
            set { text = value; }
        }
    	
        public string Url
        {
            get { return url; }
            set { url = value; }
        }
    }
  • Теперь нужно создать пользовательский элемент, который будет управлять этим вспомогательным классом.

  • Создайте заготовку пользовательского элемента управления
  • Поместите в таблицу пользовательского элемента следующие стандартные компоненты:
  • Table из вкладки HTML, который будет форматировать размещение стандартных элементов управления
  • Label из вкладки Standard в верхнюю ячейку таблицы, который будет определять заголовок набора ссылок
  • Image из вкладки HTML, определяющий рисунок маркера ссылок
  • DataList из вкладки Data в нижнюю ячейку таблицы
  • HyperLink из вкладки Standard
  • Настройте дескрипторный код пользовательского элемента управления так
  • <%@ Control Language="C#" AutoEventWireup="true" CodeFile="LinkTable.ascx.cs" 
    Inherits="LinkTable" %>
    	
    <table border="1" cellpadding="2" cellspacing="0" width="100%">
        <tr>
            <td>
                <asp:Label ID="lblTitle" runat="server"
                    ForeColor="#C00000" Font-Bold="true" 
                    Font-Names="Vernada" Font-Size="Small">
                    <!--Здесь будет заголовок-->
                </asp:Label>
            </td>
        </tr>
        <tr>
            <td>
                <asp:DataList ID="listContent" runat="server">
                    <ItemTemplate>
                        <img width="25" height="13" src="Yes.gif" alt="маркер">
                        <asp:HyperLink ID="HyperLink1" runat="server"
                            NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "Url") %>'
                            Text='<%# DataBinder.Eval(Container.DataItem, "Text") %>' />
                    </ItemTemplate>
                </asp:DataList>
            </td>
        </tr>
    </table>

    Дескриптор ). Атрибут alt задает альтернативную надпись в поле рисунка, если последний не будет найден.

    Теперь пришла пора расширить функциональность пользовательского элемента управления LinkTable. Для этого

  • Вызовите на редактирование файл LinkTable.ascx.cs и заполните его следующим кодом
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTable : System.Web.UI.UserControl
    {
        public string Title
        {
            get { return lblTitle.Text; }
            set { lblTitle.Text = value; }
        }
    	
        private LinkTableItem[] items;
        public LinkTableItem[] Items
        {
            get { return items; }
            set 
            {
                items = value;
    	
                // Обновляем сетку
                listContent.DataSource = items;
                listContent.DataBind();
            }
        }
    }

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

  • Выполните команду Window/Close All Documents, чтобы закрыть все окна редактирования
  • Откройте через панель Solution Explorer на редактирование файл LinkTableTest.aspx в режиме Design и перетащите на форму из этой панели узел LinkTable.ascx пользовательского элемента управления
  • После этого форма страницы должна выглядеть так

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

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="LinkTableTest.aspx.cs" Inherits="LinkTableTest" %>
    	
    <%@ Register Src="LinkTable.ascx" TagName="LinkTable" TagPrefix="uc1" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Тестовая страница сложного элемента управления</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <uc1:LinkTable ID="LinkTable1" runat="server" />
            </div>
        </form>
    </body>
    </html>
  • Откройте на редактирование файл поддержки страницы LinkTableTest.aspx.cs и заполните его следующим кодом
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTableTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Формируем заголовок списка
            LinkTable1.Title = "Список жизненно важных гиперссылок";
    	
            // Создаем список элементов - гиперссылок
            LinkTableItem[] items = new LinkTableItem[3];
            items[0] = new LinkTableItem("Ссылка №1 к Большому Биллу", 
                "http://www.microsoft.com");
            items[1] = new LinkTableItem("Ссылка №2 к автошколе \"Диалог-Сервис\"", 
                "http://www.dialog-service.net");
            items[2] = new LinkTableItem("Ссылка №3 к затычке JavaScript", 
                "javascript:void(0)");
            LinkTable1.Items = items;
        }
    }
  • Запустите приложение и убедитесь, что генерируемый пользовательским элементом управления набор гиперссылок работает исправно
  • Добавление событий

    Событие - это встроенный механизм C#, с помощью которого автоматически обеспечивается уведомление зарегистрированных объектов о необходимости выполнения ими какого-то действия. Объект предоставляет свои методы-обработчики для реагирования на возникновение определенного события. В этом случае говорят, что объект подписался на некоторое событие. Такой механизм позволяет отдельным объектам обмениваться сообщениями и реагировать на них.

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

    Для объявления адресной переменной события используются делегаты. Делегаты представляются специальным классом, определяющим сигнатуру (типы, количество и порядок следования аргументов) передаваемой с событием информации отправляющей стороной, и сигнатуру обработчиков события принимающей стороны. Делегат объявляется в глобальной области видимости уровня класса, чтобы сделать событие видимым во всех классах приложения. Объект, порожденный классом-делегатом, и является событием.

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

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

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

  • В панели Solution Explorer, удерживая клавишу Ctrl, выделите узлы LinkTable.ascx и LinkTableTest.aspx
  • Через контекстное меню панели выполните команду Copy, а затем через контекстное меню на узле проекта (вверху) выполните команду Paste
  • Переименуйте созданные копии в имена оригиналов, но с добавлением Ext (extension - расширенный), а именно: LinkTableExt.ascx, LinkTableExtTest.aspx (файлы поддержки LinkTableExt.ascx.cs и LinkTableExtTest.aspx.cs оболочка переименует сама)
  • Сделайте страницу LinkTableExtTest.aspx стартовой, выполнив на ее узле в представлении панели Solution Explorer команду Set As Start Page контекстного меню
  • Подправьте в файле LinkTableExtTest.aspx декларацию @Register, чтобы новая тестовая страница применяла пользовательский элемент управления из другого файла
    <%@ Register Src="LinkTableExt.ascx" TagName="LinkTable" TagPrefix="uc1" %>
  • Запустите новую тестовую страницу на выполнение, чтобы убедиться в ее работоспособности с прежней функциональностью
  • Все, теперь мы готовы модернизировать новые файлы предыдущего примера. Посмотрим, что мы хотим сделать.

    В предыдущем примере мы с помощью вспомогательного класса LinkTableItem могли добавлять в классе пользовательского элемента управления LinkTableTest новые гиперссылки типа asp:HyperLink к элементу asp:DataList. По щелчку клиента на одной из гиперссылок он безусловно направлялся по указанному адресу. Теперь же мы хотим перехватывать и распознавать щелчок на любой гиперссылке для контроля за действиями пользователя: он хочет туда-то, а можно ли ему. Вдруг пользователь хочет в закрома, а мы считаем, что он не имеет на это достаточных прав. Сейчас мы не будем оценивать пользователя, просто распознаем гиперссылку и выведем ее на экран.

    Ранее в дескрипторном представлении пользовательского элемента управления мы применяли стандартный элемент asp:HyperLink. Если посмотреть на класс этого элемента System.Web.UI.WebControls.HyperLink через панель Object Browser, то можно увидеть, что он не имеет никаких событий

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

    Эти события мы и будем перехватывать в нашем элементе управления.

  • Измените тип элемента в шаблоне <ItemTemplate> дескрипторного представления файла LinkTableExt.ascx и новое имя атрибута CommandArgument, после чего файл должен быть таким
  • <%@ Control Language="C#" AutoEventWireup="true" CodeFile="LinkTableExt.ascx.cs" 
    Inherits="LinkTable" %>
    	
    <table border="1" cellpadding="2" cellspacing="0" width="100%">
        <tr>
            <td>
                <asp:Label ID="lblTitle" runat="server"
                    ForeColor="#C00000" Font-Bold="true" 
                    Font-Names="Vernada" Font-Size="Small">
                    <!--Здесь будет заголовок-->
                </asp:Label>
            </td>
        </tr>
        <tr>
            <td>
                <asp:DataList ID="listContent" runat="server">
                    <ItemTemplate>
                        <img width="25" height="13" src="Yes.gif" alt="маркер">
                        <asp:LinkButton ID="HyperLink1" runat="server" 
                            CommandArgument='<%# DataBinder.Eval(Container.DataItem, "Url") %>'
                            Text='<%# DataBinder.Eval(Container.DataItem, "Text") %>'>
                        </asp:LinkButton>
                    </ItemTemplate>
                </asp:DataList>
            </td>
        </tr>
    </table>

    Мы хотим создать событие с именем LinkClicked. Для распознавания выбранного пункта списка нам вместе с будущим событием LinkCliked нужно будет передавать информацию о значениях свойств Text и Url, которые мы определили ранее во вспомогательном классе LinkTableItem.cs. Затем в обработчике события LinkClicked мы распознаем эту информацию и выведем ее на экран.

    Напрямую экземпляр класса LinkTableItem.cs в аргументах события передавать нельзя - не тот тип, поэтому упакуем его в класс LinkTableEventArgs, который обязан быть наследником библиотечного класса System.EventArgs. Объявим также делегата события в глобальной области видимости.

  • Откройте файл LinkTableExt.ascx.cs поддержки пользовательского элемента управления и добавьте в него объявление нового класса и делегата, после чего общий код должен быть таким
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTable : System.Web.UI.UserControl
    {
        public string Title
        {
            get { return lblTitle.Text; }
            set { lblTitle.Text = value; }
        }
    	
        private LinkTableItem[] items;
        public LinkTableItem[] Items
        {
            get { return items; }
            set 
            {
                items = value;
    	
                // Обновляем сетку
                listContent.DataSource = items;
                listContent.DataBind();
            }
        }
    }
    	
    // Объявляем делегат события LinkClicked
    // в глобальной области видимости,
    // который определит сигнатуру события и обработчика
    public delegate void LinkClickedEventHandler(object sender, LinkTableEventArgs e);
    	
    // Класс-упаковка экземпляра LinkTableItem для 
    // передачи с событием в качестве аргумента
    public class LinkTableEventArgs : EventArgs
    {
        // Конструктор
        public LinkTableEventArgs(LinkTableItem item)
        {
            selectedItem = item;
        }
    	
        // Внутреннее поле будет хранить объект гиперссылки
        // и контролируется свойством "только для чтения"
        private LinkTableItem selectedItem;
        public LinkTableItem SelectedItem
        {
            get { return selectedItem; }
        }
    	
        // Внутренне поле хранит флаг отмены, переданный сгенерировавшим 
        // событие классом-источником для обработчика класса-приемника
        private bool cancel = false;// Инициализируется при создании
        public bool Cancel
        {
            get { return cancel; }
            set { cancel = value; }
        }
    }

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

  • Откройте на редактирование файл LinkTableExt.ascx в режиме Design
  • Выделите пользовательский элемент управления и в панели Properties создайте обработчик для события
  • Добавьте в класс LinkTable и созданный обработчик стандартного события следующий код
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTable : System.Web.UI.UserControl
    {
        public string Title
        {
            get { return lblTitle.Text; }
            set { lblTitle.Text = value; }
        }
    	
        private LinkTableItem[] items;
        public LinkTableItem[] Items
        {
            get { return items; }
            set 
            {
                items = value;
    
                // Обновляем сетку
                listContent.DataSource = items;
                listContent.DataBind();
            }
        }
    	
        // Объявляем в классе наше событие LinkClicked 
        // с помощью ранее созданного глобального делегата
        public event LinkClickedEventHandler LinkClicked;
    	
        protected void listContent_ItemCommand(object source, DataListCommandEventArgs e)
        {
            if (LinkClicked != null)
            {
                // Выделяем объект ссылки, на которой был выполнен щелчок
                LinkButton link = (LinkButton)e.Item.Controls[1];
    			
                // Выделяем нужные свойства Text и Url через атрибуты
                // дескриптора элемента LinkButton и сохраняем их
                // в экземпляре вспомогательного класса
                LinkTableItem item = new LinkTableItem(link.Text, link.CommandArgument);
    			
                // Передаем ссылку на сохраненные свойства в класс упаковки аргументов
                LinkTableEventArgs args = new LinkTableEventArgs(item);
    	
                // Генерируем событие
                LinkClicked(this, args);
    	
                // Пускаем клиента к закромам по ссылке, если класс-получатель не запретил
                if (args.Cancel != true)
                {
                    Response.Redirect(item.Url);
                }
            }
        }
    }
    	
    // Объявляем делегат события LinkClicked
    // в глобальной области видимости,
    // который определит сигнатуру события и обработчика
    public delegate void LinkClickedEventHandler(object sender, LinkTableEventArgs e);
    	
    // Класс-упаковка экземпляра LinkTableItem для 
    // передачи с событием в качестве аргумента
    public class LinkTableEventArgs : EventArgs
    {
        // Конструктор
        public LinkTableEventArgs(LinkTableItem item)
        {
            selectedItem = item;
        }
    	
        // Внутреннее поле будет хранить объект гиперссылки
        // и контролируется свойством "только для чтения"
        private LinkTableItem selectedItem;
        public LinkTableItem SelectedItem
        {
            get { return selectedItem; }
        }
    	
        // Внутренне поле хранит флаг отмены, переданный сгенерировавшим 
        // событие классом-источником для обработчика класса-приемника
        private bool cancel = false;// Инициализируется при создании
        public bool Cancel
        {
            get { return cancel; }
            set { cancel = value; }
        }
    }

    Теперь событие LinkClicked будет возникать всякий раз, когда пользователь щелкнет на гиперссылке пользовательского элемента управления. Классом-получателем этого события определим нашу тестовую страницу. Именно в ней мы создадим функцию-обработчик, которая распакует переданные событием аргументы и выведет полученную информацию на экран. Проверку существования события мы выполняем на всякий случай, чтобы наверняка предотвратить генерацию системного исключения.

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

  • Откройте на редактирование файл LinkTableExtTest.aspx.cs и в конце обработчика события Page_Load() класса LinkTableTest начните набирать имя объекта LinkTable1, под которым на тестовой странице у нас числится экземпляр зарегистрированного на ней пользовательского элемента управления. Вы сразу увидите реакцию подсказчика кода IntelliSense
  • Нажав два раза клавишу Tab мы получим и регистрацию события в классе-приемнике через глобального делегата, и заготовку обработчика. После генерации оболочкой таким способом заготовки обработчика далее можно поступить одним из следующих способов:

  • Либо оставить все как есть и начать заполнять обработчик
  • Либо оставить заготовку обработчика, а код регистрации события уничтожить
    LinkTable1.LinkClicked += new LinkClickedEventHandler(LinkTable1_LinkClicked);
  • Если выбрать второй способ, тогда нужно в дескрипторном представлении пользовательского элемента добавить атрибут OnLinkClicked="LinkTable1_LinkClicked", чтобы привязать к событию созданный обработчик (обратите внимание, что имя атрибута формируется из имени события и префикса On ). Но и в этом случае Большой Билл нам тоже чуть-чуть помогает

    Давайте выберем второй способ...

  • Уничтожте регистрацию события в классе LinkTableTest, скопируйте имя сгенерированного обработчика и добавьте его в атрибут OnLinkClicked="LinkTable1_LinkClicked" дескриптора <uc1:LinkTable ID="LinkTable1" runat="server" /> файла тестовой страницы LinkTableExtTest.aspx
  • В режиме Design тестовой страницы разместите текстовую метку asp:Label из вкладки Standard, задайте ей имя lblInfo и очистите поле ее свойства Text (либо удалите одноименный атрибут в дескрипторе). В ней мы будем отображать информацию о нажатой гиперссылке
  • Вернитесь в файл LinkTableExtTest.aspx.cs и заполните обработчик так
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class LinkTableTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Формируем заголовок списка
            LinkTable1.Title = "Список жизненно важных гиперссылок";
    	
            // Создаем список элементов - гиперссылок
            LinkTableItem[] items = new LinkTableItem[3];
            items[0] = new LinkTableItem("Ссылка №1 к Большому Биллу", 
                "http://www.microsoft.com");
            items[1] = new LinkTableItem("Ссылка №2 к автошколе \"Диалог-Сервис\"", 
                "http://www.dialog-service.net");
            items[2] = new LinkTableItem("Ссылка №3 к затычке JavaScript", 
                "javascript:void(0)");
            LinkTable1.Items = items;
        }
    	
        public void LinkTable1_LinkClicked(object sender, LinkTableEventArgs e)
        {
            // Ищем подстроку в вызывающей строке
            if (e.SelectedItem.Text.IndexOf("Диалог-Сервис") > 0)
            {
                e.Cancel = false;// Пропустить
            }
            else
            {
                e.Cancel = true; // Не пускаем дальше
                lblInfo.Text = "Вы щелкнули " +
                    "\"" + e.SelectedItem.Text + "\".";
                lblInfo.Text += " Извините, но Вам не разрешено идти на " +
                    e.SelectedItem.Url + "!!!";
            }
        }
    }
  • Запустите тестовую страницу LinkTableExtTest.aspx и полюбуйтесь на свою работу
  • Доступ из кода Web-страницы к составным компонентам пользовательского элемента управления

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

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

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

    В пользовательский элемент вводят промежуточные свойства или методы доступа, через которые из Web-страницы обращаются к его компонентам. Это напоминает прием, который применяется в C++ для поддержания инкапсуляции (закрытости) данных: переменные-члены класса с модификатором доступа private упаковывают в функции доступа public, которые сами видят эти переменные, а их, в свою очередь, видят клиенты этого класса.

    Пусть, например, пользовательский элемент управления MyControl1 как объект порождается классом MyControl и в качестве одного из компонентов содержит в себе объект Label1 библиотечного класса System.Web.UI.WebControls.Label. Класс Label наследует от базового класса System.Web.UI.WebControls.WebControl свойство BackColor, которое, в свою очередь, имеет тип System.Drawing.Color. Напрямую из Web-страницы нельзя установить новое значение свойства BackColor компонента Label1 пользовательского элемента. Но можно раскрыть это свойство для Web-страницы, введя промежуточное свойство BackColor (или с другим именем) в классе самого пользовательского элемента. Сказанное схематично изображено на рисунке

    Выполним это на практике.

  • Создайте заготовку пользовательского элемента Web User Control с именем MyControl.ascx с раздельным кодом (флажок Place code in separate file мастера включен)
  • Перейдите в режим Design редактора заготовки и поместите на нее из вкладки Standard элемент Label с именем Label1
  • В панели Solution Explorer откройте файл поддержки MyControl.ascx.cs, удалите из класса MyControl заготовку обработчика Page_Load() за ненадобностью и дополните класс свойствами доступа, чтобы он стал таким
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControl : System.Web.UI.UserControl
    {
        public string Text
        {
            get { return Label1.Text; }
            set { Label1.Text = value; }
        }
    	
        public System.Drawing.Color BackColor
        {
            get { return Label1.BackColor; }
            set { Label1.BackColor = value; }
        }
    	
        public System.Drawing.Color ForeColor
        {
            get { return Label1.ForeColor; }
            set { Label1.ForeColor = value; }
        }
    }

    Здесь мы использовали имена посреднических свойств родителя MyControl1 точно такие, как у прямых свойств компонента Label1. Но это не обязательно. Можно использовать любые имена. Просто, когда Иванов, Петров, Сидоров означают одно и то же лицо, но в разных инстанция числятся по разному, это " хужее ".

  • Создайте пользовательскую тестовую страницу с раздельным кодом под именем MyControlTest.aspx и переведите ее в режим редактирования Design
  • Из панели Solution Explorer из дерева файлов приложения перенесите на тестовую страницу узел MyControl.ascx. Убедитесь, что экземпляр пользовательского элемента управления зарегистрировался на тестовой странице под именем MyControl1
  • Попробуйте выделить элемент управления и убедитесь, что он не выделяется и добавленные нами свойства не видны в панели Properties. Это значит, что декларативно (в режиме проектирования) из страницы настраивать его нелься, но можно программно управлять (в режиме выполнения) через введенные нами общедоступные свойства, в чем мы и хотим убедиться.
  • Поместите на страницу под пользовательским элементом управления компонент RadioButtonList из вкладки Standard и убедитесь, что он имеет ID="RadioButtonList1"
  • Через коллекцию Items элемента RadioButtonList1 задайте три радиокнопки с такими свойствами
  • Text="Красный"; Value="Red"
  • Text="Зеленый"; Value="Green"
  • Text="Синий"; Value="Blue"
  • Установите свойство RepeatDirection элемента RadioButtonList1 в значение Horozontal
  • Выделите компонент RadioButtonList1 и через панель Properties создайте обработчик для его события SelectedIndexChanged
  • Под списком радиокнопок поместите компонент Input (Submit) из вкладки HTML
  • Дескрипторное представление страницы будет иметь вид

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="MyControlTest.aspx.cs" 
        Inherits="MyControlTest" %>
    	
    <%@ Register Src="MyControl.ascx" TagName="MyControl" TagPrefix="uc1" %>
    
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <uc1:MyControl ID="MyControl1" runat="server" />
            <br />
            <br />
            <asp:RadioButtonList ID="RadioButtonList1" runat="server" 
                 RepeatDirection="Horizontal" 
                 OnSelectedIndexChanged="RadioButtonList1_SelectedIndexChanged">
                <asp:ListItem Value="Red">Красный</asp:ListItem>
                <asp:ListItem Value="Green">Зеленый</asp:ListItem>
                <asp:ListItem Value="Blue">Синий</asp:ListItem>
            </asp:RadioButtonList></div>
            <br />
            <input id="Submit1" type="submit" value="submit" />
        </form>
    </body>
    </html>

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

  • Заполните обработчик RadioButtonList1_SelectedIndexChanged() страницы в файле MyControlTest.aspx.cs следующим кодом
    (рис ) Код обработчика в файле MyControlTest.aspx.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControlTest : System.Web.UI.Page
    {
        protected void RadioButtonList1_SelectedIndexChanged(object sender, EventArgs e)
        {
            int index = RadioButtonList1.SelectedIndex;
    	
            switch (index)
            {
                case 0:
                    MyControl1.ForeColor = System.Drawing.Color.White;
                    MyControl1.BackColor = System.Drawing.Color.Red;
                    break;
                case 1:
                    MyControl1.ForeColor = System.Drawing.Color.Aqua;
                    MyControl1.BackColor = System.Drawing.Color.Green;
                    break;
                case 2:
                    MyControl1.ForeColor = System.Drawing.Color.Yellow;
                    MyControl1.BackColor = System.Drawing.Color.Blue;
                    break;
            }
    	
            if(index != -1)
                MyControl1.Text = RadioButtonList1.Items[index].Value;
        }
    }
  • Откомпилируйте страницу и проверьте ее работоспособность. При щелчке на кнопке код страницы меняет свойства компонента Label1, хотя он входит в состав пользовательского элемента управления MyControl1. Взаимодействие страницы с Label1 происходит через добавленные нами в пользовательский элемент управления дополнительные глобальные свойства.
  • Второй способ

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

    Поясним сказанное на предыдущем примере.

  • Скопируйте через панель Solution Explorer, как это мы делали ранее, файл тестовой страницы MyControlTest.aspx и файл пользовательского элемента управления MyControl.ascx
  • Присвойте копиям новые имена MyControlExtTest.aspx и MyControlExt.ascx соответственно (файлы сопровождающего скрытого кода оболочка переименует автоматически)
  • Сделайте страницу MyControlExtTest.aspx стартовой, выполнив на ее узле в панели Solution Explorer команду Set As Start Page
  • Измените параметр Src директивы регистрации в файле MyControlExtTest.aspx
    <%@ Register Src="MyControlExt.ascx" TagName="MyControl" TagPrefix="uc1" %>
  • Откройте файл скрытого кода MyControlExt.ascx.cs и измените в нем класс MyControl, чтобы он выглядел так
    (рис ) Добавление в класс пользовательского элемента управления свойства-ссылки на его внутренний компонент Label1 в файле MyControlExt.ascx.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControlExt : System.Web.UI.UserControl
    {
        // Раскрываем весь компонент Label1
        // Имя свойства InnerLabel1 выбрали произвольно
        // Используется только get-аксессор
        public Label InnerLabel1
        {
            get { return Label1; }
        }
    }
  • Скорректируйте в файле MyControlExt.ascx атрибут директивы Control так Inherits="MyControlExt"
  • Откройте файл MyControlExtTest.aspx.cs скрытого кода тестовой страницы и отредактируйте его для доступа к прямым свойствам компонента Label1 с использованием созданного адреса InnerLabel1
    (рис ) Доступ к прямым свойствам Label1 через адрес InnerLabel1 на странице MyControlExtTest.aspxusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControlExtTest : System.Web.UI.Page
    {
        protected void RadioButtonList1_SelectedIndexChanged(object sender, EventArgs e)
        {
            int index = RadioButtonList1.SelectedIndex;
    
            // Доступ к прямым свойствам компонента Label1 выполняем
            // через ссылку на этот компонент в родителе MyControl1
            switch (index)
            {
                case 0:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.White;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Red;
                    break;
                case 1:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.Aqua;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Green;
                    break;
                case 2:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.Yellow;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Blue;
                    break;
            }
    
            if(index != -1)
                MyControl1.InnerLabel1.Text = RadioButtonList1.Items[index].Value;
        }
    }
  • Выполните страницу и убедитесь, что получился тот же самый результат, что и в предыдущем примере при реализации первого способа
  • Какой способ выбрать - решать Вам.

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

    На протяжении этой темы мы рассматривали вопрос создания пользовательского элемента управления в режиме проектирования и последующее управление им в программном режиме. Но может потребоваться на странице подгружать и настраивать пользовательский элемент управления динамически ("на лету"). Мы уже программно создавали из библиотечного элемента управления HtmlTable объект-таблицу на странице DynamicTable.aspx. Теперь посмотрим, как это делается для пользовательского элемента управления.

    Требуется выполнить следующие шаги:

  • Создавать при каждой загрузке страницы в обработчике ее события Page.Load экземпляр класса пользовательского элемента управления, заранее подготовленного в файлах *.ascx и *.ascx.cs. Создание и регистрация экземпляра пользовательского элемента в коде страницы осуществляется статическим методом Page.LoadControl().
  • Резервировать с помощью элемента PlaceHolder место размещения для точного позиционирования на странице пользовательского элемента управления.
  • Обязательно присваивать после динамического создания значение свойства ID пользовательского элемента управления, особенно, когда создается несколько экземпляров одного и того же элемента. Это может понадобиться для однозначной идентификации элемента в коде страницы при поиске ссылки на экземпляр, выполняемом с помощью статического метода Page.FindControl(), который как раз использует значение идентификатора ID.
  • Есть некоторая специфика динамического создания пользовательского элемента управления. Нельзя создать пользовательский элемент напрямую, как это делается с библиотечным элементом управления через конструктор класса. Пользовательский элемент управления состоит не только из поддерживающего класса в файле *.ascx.cs, но и дескрипторной части файла *.ascx. Поэтому на этапе проектирования он не представлен общим классом, экземпляр которого можно было-бы создать с помощью конструктора. Экземпляр пользовательского элемента управления создается самой ASP.NET объединением этих двух частей на этапе выполнения.

  • Скопируйте через панель Solution Explorer, как это мы делали ранее, файл тестовой страницы MyControlExtTest.aspx и файл пользовательского элемента управления MyControlExt.ascx
  • Присвойте копиям новые имена MyControlDynamicTest.aspx и MyControlDynamic.ascx соответственно (файлы сопровождающего скрытого кода оболочка переименует автоматически)
  • Сделайте страницу MyControlDynamicTest.aspx стартовой, выполнив на ее узле в панели Solution Explorer команду Set As Start Page
  • Закройте все предыдущие редактируемые документы командой меню Window/Close All Documents
  • Откройте на редактирование страницу MyControlDynamicTest.aspx в режиме Design, удалите наш пользовательский элемент управления Label1 и поместите на его место из вкладки Standard стандартный элемент управления PlaceHolder
  • Перейдите в файле MyControlDynamicTest.aspx в режим Source редактора страницы и удалите полностью старый код директивы регистрации
    <%@ Register Src="MyControlExt.ascx" TagName="MyControl" TagPrefix="uc1" %>
  • На данном этапе страница в дескрипторном представлении будет такой

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="MyControlDynamicTest.aspx.cs" 
        Inherits="MyControlTest" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:PlaceHolder ID="PlaceHolder1" runat="server" />
            <br />
            <br />
            <asp:RadioButtonList ID="RadioButtonList1" runat="server" 
                                 RepeatDirection="Horizontal">
                <asp:ListItem Value="Red">Красный</asp:ListItem>
                <asp:ListItem Value="Green">Зеленый</asp:ListItem>
                <asp:ListItem Value="Blue">Синий</asp:ListItem>
            </asp:RadioButtonList>
            <br />
            <input id="Submit1" type="submit" value="submit" />
        </div>
        </form>
    </body>
    </html>

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

  • Откройте на редактирование файл скрытого кода
  • Измените код обработчика Page_Load(), чтобы он стал таким
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class MyControlTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Создаем экземпляр класса поддержки
            // пользовательского элемента управления
            MyControl MyControl1 = (MyControl)Page.LoadControl("MyControlDynamic.ascx");
            // Добавляем объект в зарезервированное место страницы
            PlaceHolder1.Controls.Add(MyControl1);
    	
            int index = RadioButtonList1.SelectedIndex;
    	
            // Доступ к прямым свойствам компонента Label1 выполняем
            // через ссылку на этот компонент в родителе MyControl1
            switch (index)
            {
                case 0:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.White;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Red;
                    break;
                case 1:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.Aqua;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Green;
                    break;
                case 2:
                    MyControl1.InnerLabel1.ForeColor = System.Drawing.Color.Yellow;
                    MyControl1.InnerLabel1.BackColor = System.Drawing.Color.Blue;
                    break;
            }
    	
            if(index != -1)
                MyControl1.InnerLabel1.Text = RadioButtonList1.Items[index].Value;
        }
    }

    Обратите внимание, что при наборе кода создания пользовательского элемента в редакторе кода механизм IntelliSense нам не помогает, потому что страница не знает, что где-то в файле MyControlDynamic.ascx существует класс MyControl.

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

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

  • Сначала нужно разработать обычную страницу, отладить ее и проверить в действии.
  • Переименовать расширение файла из aspx в ascx
  • Директиву @Page вместе с ее атрибутами следует удалить и заменить директивой @Control с требуемыми атрибутами.
  • Удалить из кода все дескрипторы <html>, <body>, <form>, <head>.
  • В коде файла поддержки заменить базовый класс Page на UserControl.
  • Если мы используем модель совмещенного кода, то нужно в директиву Control обязательно добавить атрибут ClassName="имя_файла_или_любое_имя". Он извещает ASP.NET, что код, который нужно компилировать, находится в том же файле в дескрипторах <script>. Без этой информации файл компилироваться не будет.
  • Управление динамической загрузкой многих пользовательских элементов управления (портальные каркасы)

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

  • Header.ascx - верхний колонтитул
  • TimeDisplay.ascx - кнопка-ссылка, возвращающая неформатированную и форматированную дату-время
  • LinkTable.ascx - список гиперссылок
  • LinkTableExt.ascx - список гиперссылок с предварительным анализом выбора пользователя (для этого создали свое событие)
  • MyControl.ascx - с применением промежуточных средств доступа к настройкам внутренних компонентов
  • MyControlExt.ascx - с созданием общего свойства-ссылки на встроенный компонент для доступа к любым его членам
  • MyControlDynamic.ascx - то же самое, что и MyControlExt.ascx, только применяли способ динамической загрузки пользовательского элемента на тестовую страницу
  • Для каждого из этих элементов управления разрабатывалась своя тестовая страница, на которую элемент управления помещался декларативно. Только в последнем случае мы сам пользовательский элемент не изменяли, но на тестовой странице создавали его динамически (программно). Теперь давайте на примере рассмотрим вопрос, когда на страницу нужно поместить программно сразу несколько разных пользовательских элементов управления.

  • Создайте новую страницу DynamicUserControls.aspx с раздельным кодом и сделайте ее стартовой
  • В раскрывающемся списке панели Properties выберите элемент DOCUMENT и задайте его свойству Title значение Портальные каркасы
  • В режиме Design поместите с вкладки Standard элемент Panel и присвойте ему имя Panel1 (оно генерируется автоматически). Этот элемент будет служить контейнером для других элементов управления, в том числе и для пользовательского
  • Поместите внутрь контейнера Panel1 друг за другом по вертикали стандартные элементы
  • DropDownList с автоматически сгенерированным именем DropDownList1 - для выбора загружаемого в контейнер пользовательского элемента управления
  • PlaceHolder с автоматически сгенерированным именем PlaceHolder1 - для позиционирования в контейнере пользовательского элемента управления
  • Label с автоматически сгенерированным именем Label1 - для отображения имени файла, из которого в контейнер загружен пользовательский элемент управления
  • По большому счету нам в данном примере безразлично, какие имена будут иметь экземпляры дочерних элементов. Однозначно различать их в коде мы можем по найденным ссылкам. Ссылки, в свою очередь, будем искать в именованном контейнере по типу дочернего элемента, поскольку помещенные нами элементы имеют разный тип. Но сама оболочка Visual Studio 2005 требует, чтобы на странице не было декларативно объявляемых дескрипторов с одинаковыми значениями ID.

  • Проследите в дескрипторном представлении страницы, чтобы ни один из элементов, включая контейнер, не имел атрибутов Width и Height, ограничивающих свободную расширяемость дочерних элементов
  • Заметим, что контейнер Panel1, как родитель, по умолчанию будет определять основные свойства своих дочерних элементов, такие как цвет фона, цвет переднего плана, шрифт и т.д., если только мы не определим их явным образом.

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

  • Header.ascx - Верхний колонтитул
  • TimeDisplay.ascx - Кнопка 'время-дата'
  • LinkTable.ascx - Список гиперссылок
  • LinkTableExt.ascx - Контролируемый список гиперссылок
  • MyControl.ascx - С настройкой внутренних компонентов
  • Выделите объект DropDownList1 в контейнере Place1 и заполните декларативно его свойство Items так
    Декларативные настройки объекта DropDownList1
    Items[i] Text Value
    Items[0] (None) None
    Items[1] Верхний колонтитул HeaderTest.ascx
    Items[2] Кнопка 'время-дата' TimeDisplayTest.ascx
    Items[3] Список гиперссылок LinkTableTest.ascx
    Items[4] Контролируемый список гиперссылок LinkTableExtTest.ascx
    Items[5] С настройкой внутренних компонентов MyControlTest.ascx
  • В скрытом поле Value мы указали имена файлов, которые в будущем должны будем создать
  • Установите для элемента DropDownList1 его свойство AutoPostBack в значение True, чтобы обеспечить постинг страницы при выборе пользователем нужной опции
  • Будем считать, что один из трех стандартных элементов управления контейнера, а именно список, мы заполнили декларативно. Два других элемента контейнера будем заполнять программно.

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

  • В режиме Design тестовой страницы разместите по вертикали друг за другом три копии контейнера Place1 вместе с его дочерними элементами. Проследите, чтобы контейнеры имели имена Panel1, Panel2, Panel3
  • Через раскрывающийся список панели Properties выделяйте последовательно каждый контейнер Panel1, Panel2, Panel3 и настройте их свойства так
  • BackColor=Web/Gainsboro
  • BorderWidth=1px
  • HorizontalAlign=Center
  • BorderStyle=Ridge
  • EnableViewState="False"
  • Мы закончили этап проектирования страницы для данного примера. Дескрипторное представление страницы должно быть таким

    <%@ Page AutoEventWireup="true" CodeFile="DynamicUserControls.aspx.cs" Inherits="DynamicUserControls"
        Language="C#" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Портальные каркасы</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Panel ID="Panel1" runat="server" BackColor="Gainsboro" BorderStyle="Ridge" BorderWidth="1px"
                    HorizontalAlign="Center" EnableViewState="False">
                    <asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True">
                        <asp:ListItem Value="None">(None)</asp:ListItem>
                        <asp:ListItem Value="HeaderTest.ascx">Верхний колонтитул</asp:ListItem>
                        <asp:ListItem Value="TimeDisplayTest.ascx">Кнопка 'время-дата'</asp:ListItem>
                        <asp:ListItem Value="LinkTableTest.ascx">Список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="LinkTableExtTest.ascx">Контролируемый список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="MyControlTest.ascx">С настройкой внутренних компонентов</asp:ListItem>
                    </asp:DropDownList><br />
                    <br />
                    <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
                    <br />
                    <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label></asp:Panel>
                <br />
                <asp:Panel ID="Panel2" runat="server" BackColor="Gainsboro" BorderStyle="Ridge" BorderWidth="1px"
                    EnableViewState="False" HorizontalAlign="Center">
                    <asp:DropDownList ID="DropDownList2" runat="server" AutoPostBack="True">
                        <asp:ListItem Value="None">(None)</asp:ListItem>
                        <asp:ListItem Value="HeaderTest.ascx">Верхний колонтитул</asp:ListItem>
                        <asp:ListItem Value="TimeDisplayTest.ascx">Кнопка 'время-дата'</asp:ListItem>
                        <asp:ListItem Value="LinkTableTest.ascx">Список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="LinkTableExtTest.ascx">Контролируемый список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="MyControlTest.ascx">С настройкой внутренних компонентов</asp:ListItem>
                    </asp:DropDownList><br />
                    <br />
                    <asp:PlaceHolder ID="PlaceHolder2" runat="server"></asp:PlaceHolder>
                    <br />
                    <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label></asp:Panel>
                <br />
                <asp:Panel ID="Panel3" runat="server" BackColor="Gainsboro" BorderStyle="Ridge" BorderWidth="1px"
                    EnableViewState="False" HorizontalAlign="Center">
                    <asp:DropDownList ID="DropDownList3" runat="server" AutoPostBack="True">
                        <asp:ListItem Value="None">(None)</asp:ListItem>
                        <asp:ListItem Value="HeaderTest.ascx">Верхний колонтитул</asp:ListItem>
                        <asp:ListItem Value="TimeDisplayTest.ascx">Кнопка 'время-дата'</asp:ListItem>
                        <asp:ListItem Value="LinkTableTest.ascx">Список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="LinkTableExtTest.ascx">Контролируемый список гиперссылок</asp:ListItem>
                        <asp:ListItem Value="MyControlTest.ascx">С настройкой внутренних компонентов</asp:ListItem>
                    </asp:DropDownList><br />
                    <br />
                    <asp:PlaceHolder ID="PlaceHolder3" runat="server"></asp:PlaceHolder>
                    <br />
                    <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label></asp:Panel>
                <br />
            </div>
        </form>
    </body>
    </html>
  • Выполните сформированную страницу DynamicUserControls.aspx, чтобы убедиться в работоспособности настроек режима проектирования. Результат на данном этапе должен быть таким
  • Таким образом, мы выполнили интерфейсную заготовку портальных каркасов на тестовой странице. Теперь пришла пора наполнить их функциональностью, которая бы обеспечила контейнерную загрузку выбранных пользовательских элементов управления. Эту функциональность мы обеспечим, кодируя файл DynamicUserControls.aspx.cs скрытого кода страницы. Ниже мы убедимся, что программно загруженные в портальные каркасы пользовательские элементы управления обрабатывают свои события точно также, как будто бы каждый из них находится на отдельной тестовой странице.

  • Откройте на редактирование файл DynamicUserControls.aspx.cs
  • Добавьте в класс DynamicUserControls следующий код
  • using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class DynamicUserControls : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Загружаем портальные каркасы
            MyLoadControls(Panel1);
            MyLoadControls(Panel2);
            MyLoadControls(Panel3);
        }
    	
        // Вспомогательная функция
        private void MyLoadControls(Control containerPanel)
        {
            // Объявляем вспомогательные ссылки на интерфейсные
            // элементы текущего контейнера
            DropDownList list = null;
            PlaceHolder ph = null;
            Label lbl = null;
    	
            // Перебираем все дочерние элементы текущего 
            // контейнера и ищем ссылки на объекты дочерних
            // интерфейсных элементов по их типам
            foreach (Control ctrl in containerPanel.Controls)
            {
                if (ctrl is DropDownList)
                    list = (DropDownList)ctrl;
                else if (ctrl is PlaceHolder)
                    ph = (PlaceHolder)ctrl;
                else if (ctrl is Label)
                    lbl = (Label)ctrl;
            }
    	
            // Берем информацию из выделенного
            // элемента списка текущего контейнера
            string lstValue = list.SelectedItem.Value;
            string lstText = list.SelectedItem.Text;
            lbl.Text = "";
    	
            // Заполняем текущий контейнер.
            // Проверяем наличие нужного расширения
            if (lstValue.EndsWith(".ascx"))
            {
                ph.Controls.Add(Page.LoadControl(lstValue));
                lbl.Text = "Загрузили: " + lstValue + " - " + lstText;
            }
        }
    }

    Создадим пользовательские элементы управления, которые реализуют пользовательский интерфейс и функциональность, использованную нами при выполнении примеров в тестовых страницах. Из этих тестовых страниц мы их и переделаем.

  • В панели Solution Explore выделите при нажатой клавише Ctrl следующие файлы
  • HeaderTest.aspx
  • TimeDisplayTest.aspx
  • LinkTableTest.aspx
  • LinkTableExtTest.aspx
  • MyControlTest.aspx
  • Выполните команду Copy затем Paste (на верхнем узле проекта) контекстного меню
  • Присвойте копиям те же самые имена, только с расширением .ascx
  • HeaderTest.ascx
  • TimeDisplayTest.ascx
  • LinkTableTest.ascx
  • LinkTableExtTest.ascx
  • MyControlTest.ascx
  • Откройте новые файлы в дескрипторном режиме и замените директиву @Page на @Control, все остальные атрибуты директивы @Page оставьте прежними. Теперь это будут не страницы, а пользовательские элементы управления. Директиву @Register с ее атрибутами оставьте в покое
  • Удалить из кода все дескрипторы <html>, <body>, <form>, <head> и закрывающие их дескрипторы.
  • Для новых файлов *.ascx откройте файлы скрытого кода *.ascx.cs и замените в объявлении класса имя наследующего класса с " : System.Web.UI.Page " на " : System.Web.UI.UserControl ". Остальной код классов останется прежним.
  • Мы закончили выполнение примера. Код тестовой страницы при очередной загрузке будет программно создавать свои пользовательские элементы управления для каждого контейнера Panel и размещать их в элементах PlaceHolder.

  • Выполните страницу DynamicUserControls.aspx и испытайте ее. Результат будет примерно таким
  • Иногда при отладке приложения оболочка начинает сбоить, выдавая непонятные ошибки. Может помочь перезапуск Visual Studio 2005 или удаление файла web.config, все равно оболочка создаст его автоматически при очередной компиляции. Пока нам этот файл не жалко, потому что его настройками мы все-равно еще не занимались.
    Вернуться к учебному плану