Опубликован: 07.05.2010 | Доступ: свободный | Студентов: 1678 / 62 | Оценка: 4.56 / 4.06 | Длительность: 34:11:00
Лекция 6:

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

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

Событие - это встроенный механизм 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 создайте обработчик для события ItemCommand (Проследите, чтобы в дескрипторе пользовательского элемента на тестовой странице не появился атрибут Width )


  • Добавьте в класс 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 мы получим и регистрацию события в классе-приемнике через глобального делегата, и заготовку обработчика. После генерации оболочкой таким способом заготовки обработчика далее можно поступить одним из следующих способов:

  1. Либо оставить все как есть и начать заполнять обработчик
  2. Либо оставить заготовку обработчика, а код регистрации события уничтожить
    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 и полюбуйтесь на свою работу