Пользовательские компоненты
Упражнение 3. Создание невизуального компонента-будильника AlarmClock (тревожные часы) с собственным событием
Событие (event) можно определить как любое происшествие, вызванное вмешательством пользователя, операционной системы или логикой программы. Любое событие можно обнаружить и нужным образом на него отреагировать с помощью обработчиков события. Событие в пользовательском классе определяется с помощью делегата, задающего сигнатуру потенциальных обработчиков и содержащего список указателей на все подписавшиеся на это событие обработчики. Если событие имеет аргументы, то через них можно передавать любую информацию в обработчики этого события.
Событие может прослушивать любой блок кода программы, в котором используется экземпляр класса, генерирующего событие, если только этот код зарегистрирует у себя это событие и создаст обработчик. События являются удобным механизмом общения объектов между собой и создания нужных реакций на те или иные происшествия. Событие объявляется в пользовательском классе как специальное публичное свойство для множественной адресации потенциальных обработчиков.
В классе также создается одноименный событию защищенный виртуальный метод, обычно с префиксом On, для генерации события при наступлении определенного происшествия. Этот метод называется методом диспетчеризации события (event-dispatching method). Метод диспетчеризации, перед генерацией события, должен проверять, существует ли хотя бы один зарегистрированный обработчик этого события, чтобы не адресоваться к нулевому обработчику. Он также устанавливает значения фактических аргументов, передаваемых обработчикам. При регистрации обработчика события в клиентском коде поддерживаются только операции += и -=.
Рассмотрим механизм работы событий на примере создания невизуального компонента AlarmClock (будильник), передающего оповещение по прошествии указанного времени. Хотя встроить подобный код внутрь самого приложения вовсе не сложно, создание такого компонента позволит отделить его от остального исходного кода и использовать многократно в других приложениях (а удачные компоненты можно и продавать). В качестве объекта, инициирующего генерацию нашего события, будем использовать экземпляр системного таймера, определенного библиотечным компонентом Timer.
- Добавьте к проекту MyComponents новый файл с именем AlarmClock.cs типа Component
- Переведите редактирование компонента AlarmClock в режим Design и двойным щелчком в панели Toolbox добавьте к компоненту экземпляр системного таймера Timer с именем timer1
- Выделите объект timer1 и через панель Properties установите свойство Interval=1000
- Переведите панель Properties в режим Events, установите для события Tick срабатывания системного таймера название обработчика TimerHandler и нажмите клавишу Enter
Оболочка добавит в файл AlarmClock.cs заготовку обработчика TimerHandler события срабатывания системного таймера
using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; //------------------------------------------------------------- // Часть 0 расположена в отдельном файле AlarmClock.Designer.cs //------------------------------------------------------------- // Часть 1 namespace MyCompany.MyComponents { public partial class AlarmClock : Component { public AlarmClock() { InitializeComponent(); } public AlarmClock(IContainer container) { container.Add(this); InitializeComponent(); } // Обработчик события срабатывания системного таймера, созданного, // настроенного и зарегистрированного через визуальный конструктор. // Обработчик выполняется каждую секунду и проверяет, пришла ли пора // генерировать наше событие, на которое подпишется клиент компонента private void TimerHandler(object sender, EventArgs e) { } } }Листинг 24.10. Начальное содержимое класса компонента в файле AlarmClock.cs
Для удобства класс компонента разбьем на отдельные части по функциональной принадлежности, что никак не повлияет на логику выполнения программы. Для еще большего удобства эти части класса можно размещать в отдельных файлах проекта компонентов, например, AlarmClockPart1.cs, AlarmClockPart2.cs, и т.д. (на любителя...).
- Выделите заготовку обработчика в отдельную часть, которую мы заполним позднее
// Часть 4 namespace MyCompany.MyComponents { // Часть класса с определением обработчика события Tick // системного таймера System.Windows.Forms.Timer timer1 partial class AlarmClock { // Обработчик события срабатывания системного таймера, созданного, // настроенного и зарегистрированного через визуальный конструктор. // Обработчик выполняется каждую секунду и проверяет, пришла ли пора // генерировать наше событие, на которое подпишется клиент компонента private void TimerHandler(object sender, EventArgs e) { } } }Листинг 24.11. Обработчик события системного таймера в файле AlarmClock.cs
- Добавьте в файл AlarmClock.cs часть класса, содержащую определения свойств компонента для его настройки как в режиме проектирования, так и выполнения
// Часть 2 namespace MyCompany.MyComponents { // Часть класса с определением полей и свойств partial class AlarmClock { // Объявляем внутренние поля bool alarmFired = false; // Состояние будильника (запущен/незапущен) DateTime alarmTime = DateTime.Now; // Хранит время запуска будильника // Вложенный класс аргументов, доступных в обработчике // Расширяет библиотечный класс аргументов событий public class AlarmEventArgs : EventArgs { // Поле DateTime time; // Свойство public DateTime Time { get { return time; } set { time = value; } } } // Свойство возвращает текущее время компьютера public DateTime CurrentTime { get { return DateTime.Now; } } // Свойство для чтения и установки времени запуска будильника public DateTime AlarmTime { get { return alarmTime; } set { if (value != alarmTime) { alarmTime = value; alarmFired = false; } } } // Свойство для проверки состояния хода системного таймера, // его остановки и запуска public bool Enabled { get { return timer1.Enabled; } set { timer1.Enabled = value; if (value) alarmFired = false; } } } }Листинг 24.12. Определение свойств в файле AlarmClock.cs
Добавление в компонент пользовательского события стандартным способом
В этом разделе мы добавим к классу пользовательское событие типовым способом, как это принято в большинстве приложений. Альтернативный способ, позволяющий контролировать добавление и удаление обработчиков из списка объекта-делегата, мы рассмотрим позднее.
- Добавьте в файл AlarmClock.cs часть класса, содержащую определение собственного события стандартным способом без использования базового поля
// Часть 3 namespace MyCompany.MyComponents { // Часть класса с определением собственного события // стандартным способом без использования базового поля partial class AlarmClock { // Объявляем делегат для события. В аргументах делегата // нужно указать полное имя вложенного класса public delegate void AlarmHandler(object sender, AlarmClock.AlarmEventArgs e); // Объявляем событие с доступностью не ниже делегата public event AlarmHandler Alarm; // Метод диспетчеризации события. Виртуальный и защищенный // для возможности переопределения в будущих потомках protected virtual void OnAlarm(AlarmEventArgs args) { // Проверяем наличие зарегистрированных // обработчиков и генерируем событие if (Alarm != null) Alarm(this, args); } } }Листинг 24.13. Определение события стандартным способом в файле AlarmClock.cs
- Модифицируйте часть класса с обработчиком события Tick системного таймера, который мы создали ранее и который вызовет наше событие при наступлении времени alarmTime срабатывания будильника
// Часть 4 namespace MyCompany.MyComponents { // Часть класса с определением обработчика события Tick // системного таймера System.Windows.Forms.Timer timer1 partial class AlarmClock { // Обработчик события срабатывания системного таймера, созданного, // настроенного и зарегистрированного через визуальный конструктор. // Обработчик выполняется каждую секунду и проверяет, пришла ли пора // генерировать наше событие, на которое подпишется клиент компонента private void TimerHandler(object sender, EventArgs e) { DateTime now; // Текущее системное время AlarmEventArgs args; // В режиме разработки не выполнять if (!this.DesignMode) { now = DateTime.Now; // Если будильник не запущен и пришла пора запускать if (!alarmFired && now >= alarmTime) { // Создаем объект для передачи аргументов в событии args = new AlarmEventArgs(); // Заполняем объект текущим временем args.Time = now; // Вызываем метод диспетчеризации события Alarm this.OnAlarm(args); // Поднимаем флаг "Будильник запущен" alarmFired = true; } } } } }Листинг 24.14. Обработчик события Tick системного таймера timer1 в файле AlarmClock.cs
- Откомпилируйте библиотеку наших компонентов, выполнив команду Build/Build MyComponents
- Перейдите в режим редактирования Form1.cs [Design] и убедитесь, что в панели Toolbox после компиляции появился новый компонент AlarmClock