Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSBasicCourse2ndedPodbelsky / CSBasicCourse2ndedPodbelsky.rtf
Скачиваний:
27
Добавлен:
22.03.2016
Размер:
11.9 Mб
Скачать

17.6. События

Событие "событие" – средство, позволяющее объекту (или классу) послать

во "внешний для объекта или класса мир" сообщение о переходе в некоторое новое

состояние или о получении сообщения из внешнего источника. Так как на уровне

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

посылка сообщения оформляется как оператор в теле некоторого метода.

Синтаксически оператор посылки сообщения "посылки сообщения" выглядит так:

имя_события (аргументы_для_делегата);

Разберем, о каком событии идет речь и какую роль играет делегат, которому

нужно предоставить аргументы.

Отметим, что объявление события может размещаться в классе и в

интерфейсе. Начнём с классов. Событие это член класса (или его объекта),

вводимый объявлением "событие:объявление события" \y "события" :

модификаторыopt event имя_делегата имя_события;

модификатором может быть abstract, new, override, static, virtual, public,

protected, private, internal.

event – служебное слово

"служебное слово: event" \y "служебное слово"

декларации события.

имя_события – идентификатор, выбираемый программистом в качестве названия

конкретного

члена,

называемого

переменной

события.

В

практике

программирования на C# принято начинать имена событий с префикса on.

имя_делегата – имя того делегата-типа (его называют делегатом события или

событийным делегатом), который должен представлять событию те методы,

которые будут вызываться в ответ на посылку сообщения о событии.

Таким образом, событие это член класса, имеющий тип делегата. Этот

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

определение должно быть в том же файле. Событийный делегат-тип определяет для

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

сообщения о нём. Напомним, что в сигнатуру "сигнатура" , вводимую делегатом,

входит спецификация параметров метода и тип возвращаемого методом значения. В

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

посылки сообщения о событии. В качестве типа возвращаемого значения в

событийном делегате обычно используется void.

В качестве примера определим статический метод, посылающий через каждую

секунду сообщение о событии. Чтобы такая возможность появилась, необходимо,

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

доступен

соответствующий

этому

событию

делегат-тип.

Соответствующие

объявления могут быть такими:

delegate void ТimeHandler();

// объявление делегата-типа

static event ТimeHandler onTime; // объявление события

Рекомендуется в название событийного делегата включать в качеств

е

суффикса слово Handler (обработчик). Делегат ТimeHandler в соответствии с его

объявлением предназначен "представлять" методы без параметров, с возвращаемым

значением типа void (ничего не возвращающие в точку вызова). Событие с именем

onTime "настроено" на работу с экземплярами делегата ТimeHandler.

В том же классе, где размещено объявление события и доступен делегат-тип,

можно определить метод, через каждую секунду "генерирующий" посылку

сообщений:

static void run() { // процесс с событиями

Console.WriteLine("Для выхода нажмите Ctrl+C!");

while (true) { // бесконечный цикл

onTime(); // посылка сообщения о событии

System.Threading.Thread.Sleep(1000); // задержка на 1 секунду

}

}

В методе run() бесконечный цикл, в каждой итерации которого оператор

onTime() посылает сообщение о событии, связанном с делегатом TimeHandler. Затем

вызывается статический метод Sleep() "метод:метод Sleep()" класса Thread "класс:

Thread" \y "класс" из пространства имен System.Threading "пространство имен

System.Threading" . Назначение этого метода состоит в "задержке" процесса

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

аргумента. В данном случае задержка равна 1000 миллисекундам, то есть одной

секунде.

Метод run(), посылая сообщения о событиях, "ничего не знает" о том, кто

будет получать эти сообщения, и как они будут обрабатываться. В технологии

Windows-программирования принято говорить, что объект (в нашем примере не

объект, а статический метод класса) публикует события

"события:публикует

события" , посылая сообщения о них. Другие объекты (в нашем примере это будут

статические методы) могут подписаться на события.

Подписка на получение сообщений о событии в языке C# предусматривает

следующие действия

:

создание экземпляра того делегата, на который настроено событие;

подключение экземпляра делегата к событию.

Обычно эти два действия объединяют в одном операторе следующего формата:

имя_события += new имя_делегата (имя_метода);

Условие применимости подписки на событие "события:подписка на событие"

– наличие и доступность метода, который будет вызван для обработки события. Имя

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

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

На одно событие могут быть подписаны несколько методов, для каждого из

которых нужно использовать свой оператор приведенного вида.

Предположим, что "принимать сообщения" о событиях в методе run() должен

метод Main() того же класса, в котором определен метод run(). Пусть

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

static void one( )

static void two( )

Тогда метод Main может быть таким (программа 17_08.cs):

static void Main() {

onTime += new TimeHandler(one); // подписка на событие...

onTime += new TimeHandler(two); // ...для метода two

run(); // запуск процесса

}

Остальное, то есть функциональность программы в целом, зависит от

возможностей и особенностей методов one() и two(). В следующей ниже программе

метод one() выводит в консольное окно дату и посекундно изменяющееся значение

времени. Метод two() в начале той же строки выводит порядковый номер события.

Номер изменяется каждую секунду (при каждом обращении к методу).

// 17_08.cs - статические события и статические методы

using System;

delegate void TimeHandler( ); // объявление делегата-типа

class test_cs {

static event TimeHandler onTime;// объявление события

static void run( ) // процесс с генерацией событий

{

Console.WriteLine("Для выхода нажмите Ctrl+C!");

while (true) { // бесконечный цикл

onTime( ); // посылка сообщения о событии

System.Threading.Thread.Sleep(1000); // задержка на 1 сек.

}

}

static void Main( ) {

onTime += new TimeHandler(one); // подписка на событие...

onTime += new TimeHandler(two); // для метода two

run(); // запуск процесса

}

static void one( ) { // приемник сообщения

string newTime = DateTime.Now.ToString();

Console.Write("\r\t\t{0}", newTime);

}

static int count = 0;

static void two( ) { // приемник сообщения

Console.Write("\r{0}", count++);

}

}

Результат выполнения программы на 7-й секунде:

Для выхода нажмите Ctrl+C!

7 16.05.2009 10:27:17

В

тексте

программы

обратим

внимание

на

вывод

предупреждения

пользователю:

Console.WriteLine("Для выхода нажмите Ctrl+C!");

Сочетание клавиш Ctrl и C приводит к незамедлительному прекращению

выполнения программы. Чтобы не "затемнять" основную схему программы,

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

средства диалога с пользователем.

В строке-аргументе метода консольного вывода Write() управляющая эскейп-

последовательность \r обеспечивает при каждом обращении переход в начало строки

дисплея. Тем самым вывод все время выполняется в одну и ту же строку,

изображение на которой обновляется ежесекундно.

В методе one() используется свойство Now "свойство: Now" класса Data.Tim

e

"класс: Data.Time" \y "класс" . Его назначение – вернуть текущее значение даты и

времени. Применение метода ToString() позволяет представить эти значения в виде

одной строки, которая затем выводится на дисплей.

Для подсчета событий (секунд) определена статическая переменная int count.

Ее значение выводит и затем увеличивает на 1 метод two().

В рассмотренном примере делегат объявлен вне классов и все методы

статические – генерацию событий выполняет статический метод run(), подписаны на

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

событий взаимодействуют не объекты, а методы одного класса test_cs. Кроме того,

в объявлении делегата отсутствуют параметры. Поэтому при посылке сообщения о

событии методы обработки не получают никакой информации из точки

возникновения события.

Более общий случай – событие создаётся объектом, а в других объектах (в

объектах других классов) имеется возможность реагировать на эти события. Как

мы уже показали, к одному событию может быть "приписано" несколько

обработчиков и все они будут вызваны при наступлении события.

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

Объявление делегата-типа, задающего сигнатуру тех (ещё неизвестных на

данном этапе) методов, которые будут вызываться при обработке события;

Определение переменной события, имеющей тип делегата события;

Определения

генератора

события

(посылки

сообщения),

с

указанием

аргументов,

информирующих

получателей

о

состоянии

объекта,

пославшего сообщение;

Определение методов обработки события. Сигнатура каждого метода должна

соответствовать типу делегата события;

Создание экземпляра того делегата, на который "настроено" событие.

Аргумент конструктора – имя метода обработки;

Подключение экземпляра делегата к переменной события

.

Перечисленные этапы обычно относятся к разным классам программы. И этих

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

содержать методы обработки или по крайней мере иметь доступ к этим методам. В

нём реализуются этапы 4, 5, 6. Второй класс – это класс, генерирующий события,

реализует этапы 1, 2, 3.

Зачастую в программе присутствует третий класс – управляющий процессом

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

монитором, диспетчером и т.п. При наличии такого класса и двух подчинённых –

класса генерации и класса обработки событий

"класс: обработки событий" \y

"класс" - схема работы супервизора сводятся к следующим шагам:

Создать объект класса генерации событий

"класс: генерации событий" \y

"класс" ;

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

метод обработки является статическим);

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

Подключить экземпляр делегата к переменной события из объекта класса

генерации;

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

методов, генерирующих события).

Далее всё выполняется в соответствии с общими принципами событийного

управления.

В качестве примера рассмотрим программу с делегатом и четырьмя классами.

Класс Sorting содержит метод, который сортирует в порядке возрастания

одномерный целочисленный массив. В процессе сортировки подсчитывается

количество перестановок значений элементов. При каждом завершении внутреннего

цикла

формируется

событие,

передающее

"во

внешний

мир"

количество

выполненных перестановок, размер массива и счётчик итераций внешнего цикла.

Для работы с событиями в классе объявлено событие onSort, имеющее ти

п

внешнего делегата SortHandler.

Класс View содержит метод обработки события. Метод выводит на консоль

значение счётчика перестановок.

Класс display визуализирует динамику процесса сортировки – выводит на

консоль имитацию элемента управления ProgressBar.

Метод Main() управляющего класса Controller в соответствии с общей схемой

создаёт объект класса, генерирующего события, создаёт объект класса –

обработчика (View). Затем подключает к переменной события два наблюдателя –

два безымянных экземпляра делегата SortHandler. И, наконец, управление

передаётся методу сортировки объекта – генератора событий.

// 17_09.cs - события и сортировка

using System;

using System.Text;

// Объявление делегата-типа:

public delegate void SortHandler(long cn, int si, int kl);

class Sorting // класс сортировки массивов

{

Соседние файлы в папке CSBasicCourse2ndedPodbelsky