Язык программирования Перфолента.Net - Официальный сайт

 Язык программирования Перфолента.Net - Официальный сайт.

Поиск   
Главная :: О проекте :: Контакты :: Обратная связь :: Благодарности :: ВходГость

   >   >   >   > 

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


Конструируем класс. События.

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


Важное событие в жизни программиста!

«Чему быть, того не миновать. А что случилось — того уже не изменишь.»

Харуки Мураками.

В старые добрые времена, когда компьютеры работали под DOS, работающая программа получала весь компьютер в единоличное владение. Она делала всё что хотела, читала и писала в любое место памяти, настраивала под себя оборудование и даже могла выключить компьютер (с тех пор, как в блоках питания появилась такая возможность). Если программа была интерактивной и активно общалась с пользователем, то большую часть времени она висела в цикле, проверяя не нажата ли клавиша на клавиатуре или мыши.

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

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

 

Терминология.

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

 

Внешние события.

Как это ни странно звучит, но современный компьютер состоит из множества более мелких компьютеров. Не знали? У видеокарты есть и процессор, и память, и устройства ввода вывода. То же самое и со многими другими устройствами. Даже мышь — это маленький компьютер, который общается с центральным процессором через шину USB, а беспроводная мышь, еще и через радиоканал.

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

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

 

Внутренние события.

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

 

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

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

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

Способы подписки на события могут отличаться в зависимости от того, что является источником события. Для программ, основанных на .Net, источники события делятся на два больших класса – управляемые источники и НЕ управляемые. Управляемые источники событий находятся под полным контролем исполнительной среды .Net, а НЕ управляемые находятся под контролем операционной системы.

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

Когда событие произойдет, исполнительная среда .Net или операционная система вызовут указанный метод и событие будет обработано программой.

 

Источники событий.

Источниками событий в языке Перфолента.Net выступают классы и созданные на их основе экземпляры объектов. Если событие является общим для класса, то источником события является класс, в противном случае источником события будет экземпляр объекта, созданный во время выполнения программы. Про типы, классы и объекты можно прочитать в статье «Терминология: классы, объекты и типы в языке Перфолента».

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

 

События и делегаты.

В .Net, а, следовательно, и в языке Перфолента, механизм событий основан на делегатах.

Событие – это член класса, имеющий производный тип от типа БазовыйДелегат или БазовыйМультиделегат.

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

Делегат определяет тип события.

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

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

Делегат Процедура ДелПроц(Стр тип Строка, Цел тип Целое)

Теперь у нас в программе имеется делегат ДелПроц и мы можем создавать события, основанные на этом типе делегата.

Умнику на заметку: В объявлении делегата ключевое слово Процедура не является обязательным и его можно пропустить или указать универсальное ключевое слово Метод.

Внимание! В качестве обработчиков событий могут быть использованы только процедуры, поэтому делегат-функция не подойдёт для создания события.

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

Подробнее про делегаты можно прочитать в статье «Конструируем класс. Делегаты.».

 

Объявление события.

Объявление события очень похоже на объявление свойства. Существует два способа объявления события: Авто (краткий способ) и Полное (подробный способ). В способе Авто достаточно указать только имя события и его тип, в способе Полное необходимо написать логику работы с делегатами при их добавлении и удалении в процессе подписки/отписки и логику вызова события.

Рассмотрим сначала краткое Авто объявление события:

Событие Авто МоёСобытие тип ПроцедураБезПараметров

Здесь МоёСобытие это имя события, а ПроцедураБезПараметров - тип события. Как мы уже знаем, тип события — это делегат, который был объявлен ранее. Например, так:

Делегат ПроцедураБезПараметров

Умнику на заметку: В кратком объявлении события ключевое слово Авто не является обязательным и его можно пропустить, а ключевое слово Полное в полном определении события является обязательным.

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

События МоёСобытие1, МоёСобытие2 тип ПроцедураБезПараметров

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

&ВидноВсем
Событие Полное МоёПолноеСобытие тип ПроцедураСПараметрами1
    Поле ПолеСобытия_МоёПолноеСобытие тип ПроцедураСПараметрами1
    Добавить(Значение тип ПроцедураСПараметрами1)
        ДобавитьДелегат ПолеСобытия_МоёПолноеСобытие, Значение
        ВыводСтроки "Добавлен делегат подписчика события МоёПолноеСобытие.")
    КонецДобавить
    Удалить(Значение тип ПроцедураСПараметрами1)
        УдалитьДелегат ПолеСобытия_МоёПолноеСобытие, Значение
        ВыводСтроки "Удалён делегат подписчика события МоёПолноеСобытие.")
    КонецУдалить
    Вызвать(Параметр1 тип Объект)
        ПолеСобытия_МоёПолноеСобытие.Выполнить(Параметр1)
        ВыводСтроки "Выполнен вызов события МоёПолноеСобытие.")
    КонецВызвать
КонецСобытия

В полном способе мы объявили Поле для хранения делегата и три процедуры Добавить, Удалить и Вызвать.

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

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

Компилятор Перфоленты позволяет в методах Добавить и Удалить опустить определение параметра и написать немного короче.

Добавить
    ДобавитьДелегат ПолеСобытия_МоёПолноеСобытие, Значение
КонецДобавить
Удалить
    УдалитьДелегат ПолеСобытия_МоёПолноеСобытие, Значение
КонецУдалить

В этом случае подразумевается, что параметр объявлен по умолчанию с именем Значение.

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

 

Подписка на событие.

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

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

Получение делегата на метод-обработчик события выполняется специальной функцией ПолучитьДелегат:

ОбработчикМоегоСобытия = ПолучитьДелегат(, ОбработкаМоегоСобытия)

Затем мы добавляем этот делегат к событию, тем самым, осуществляя подписку на него. Это можно сделать несколькими способами:

// складываем делегаты события и обработчика
МоёСобытие
+= ОбработчикМоегоСобытия
// то же самое через ЭтотОбъект
ЭтотОбъект.МоёСобытие += ОбработчикМоегоСобытия
// используем оператор ДобавитьОбработчик
ДобавитьОбработчик МоёСобытие, ОбработчикМоегоСобытия
// то же самое через ЭтотОбъект
ДобавитьОбработчик ЭтотОбъект.МоёСобытие, ОбработчикМоегоСобытия

Можно совместить получение делегата и добавление его событию в одной строке:

ДобавитьОбработчик МоёСобытие, ПолучитьДелегат(, ОбработкаМоегоСобытия)

Для полного понимания приведём процедуру-обработчик события на которую мы получали делегат в этих примерах:

Процедура ОбработкаМоегоСобытия
    ВыводСтроки "Сработал обработчик события МоёСобытие..."
КонецПроцедуры

 

Операторы ДобавитьОбработчик и УдалитьОбработчик.

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

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

Если объект А подписан на событие объекта Б, то объект Б не будет собран сборщиком мусора до тех пор, пока не будет собран объект А. Это значит, что подписка на событие может остаться единственной ссылкой, которая удерживает объект в памяти и не позволяет сборщику мусора удалить его.

Синтаксис операторов ДобавитьОбработчик и УдалитьОбработчик:

ДобавитьОбработчик ОпределениеСобытия, ДелегатОбработчикаСобытия

УдалитьОбработчик ОпределениеСобытия, ДелегатОбработчикаСобытия

где, ОпределениеСобытия – это выражение, указывающее на событие, к которому будет добавлен обработчик, а ДелегатОбработчикаСобытия – это выражение возвращающее делегат на процедуру обработчик события.

 

Вызов события.

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

Для вызова события используется оператор ВызватьСобытие:

ВызватьСобытие ИмяСобытия(ПараметрыДелегатаСобытия)

где, ИмяСобытия – это выражение, определяющее вызываемое событие, а ПараметрыДелегатаСобытия – это список фактических параметров события, определяемый параметрами делегата, который является типом события.

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

Класс Кот
    &ВидноВсем
    События КотПроголодался, КотХочетСпать тип ОбработчикСобытияБезПараметров
    &ВидноВсем
    Поля ВремяПоследнегоПитания, ВремяПоследнегоСна тип Дата
    //---------------------------
    &ВидноВсем
    Процедура ПокормитьКота
        ВремяПоследнегоПитания = ТекущаяДата()
        ВыводСтроки "Покормили кота..."
    КонецПроцедуры
    //---------------------------
    &ВидноВсем
    Процедура РазбудитьКота
        ВремяПоследнегоСна = ТекущаяДата()
        ВыводСтроки "Разбудили кота..."
    КонецПроцедуры
    //---------------------------
    &ВидноВсем
    Процедура ПроверитьСостояниеКота
        Если ТекущаяДата()-ВремяПоследнегоПитания > 3*3600
            ВызватьСобытие КотПроголодался(ЭтотОбъект)
        КонецЕсли
        Если ТекущаяДата()-ВремяПоследнегоСна > 12*3600
            ВызватьСобытие КотХочетСпать(ЭтотОбъект)
        КонецЕсли
    КонецПроцедуры
КонецКласса

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

#ТипСборки КонсольноеПриложение
#ИспользоватьСтандартнуюБиблиотеку

//***************************
Программа
    //---------------------------
    Процедура Старт
        
        ДатаНачалаОпеки = ТекущаяДата()
        
        Васька = Новый Кот
        ДобавитьОбработчик Васька.КотПроголодался, ПолучитьДелегат(,ОбработчикГолодногоКота)
        ДобавитьОбработчик Васька.КотХочетСпать, ПолучитьДелегат(,ОбработчикКотаЖелающегоСпать)
        
        Пока ТекущаяДата()-ДатаНачалаОпеки < 30 Цикл
            Васька.ПроверитьСостояниеКота
        КонецЦикла
        
        УдалитьОбработчик Васька.КотПроголодался, ПолучитьДелегат(,ОбработчикГолодногоКота)
        УдалитьОбработчик Васька.КотХочетСпать, ПолучитьДелегат(,ОбработчикКотаЖелающегоСпать)
        
        Васька = Неопределено

        ВыводСтроки "СМЕНА ОКОНЧЕНА!!!"
        ВводСтроки
    КонецПроцедуры
    //---------------------------
    Процедура ОбработчикГолодногоКота(ЭтотКот тип Объект)
        ВыводСтроки "Кот голодный!!!"
        ТипКТипу(ЭтотКот,"Кот").ПокормитьКота
    КонецПроцедуры
    //---------------------------
    Процедура ОбработчикКотаЖелающегоСпать(ЭтотКот тип Объект)
        ВыводСтроки "Кот хочет спать!!!" 
        ТипКТипу(ЭтотКот,"Кот").РазбудитьКота
    КонецПроцедуры
КонецПрограммы

 

Делегаты для создания событий в стандартной библиотеке.

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

Самые полезные из них:

Делегат Процедура ОбработчикСобытияБезПараметров(Источник тип Объект)
Делегат Процедура ОбработчикСобытия(Источник тип Объект, Параметры тип ПараметрыСобытия)
Делегат Процедура ОбработчикСобытияСОтменой(Источник тип Объект, Параметры тип ПараметрыСобытияСОтменой)
Делегат Процедура ОбработчикПередДействием(Источник тип Объект, Ссыл Отказ тип Булево, Ссыл СтандартнаяОбработка тип Булево)
Делегат Процедура ОбработчикПереопределяемогоДействия(Источник тип Объект, Ссыл СтандартнаяОбработка тип Булево)
Делегат Процедура ОбработчикСтрокиДанных(Источник тип Объект, СтрокаДанных тип Строка)
Делегат Процедура ОбработчикВыбораОбъекта(Источник тип Объект, ВыбранныйОбъект тип Объект, Ссыл СтандартнаяОбработка тип Булево)

Если Вам подходит по смыслу один из этих делегатов, то используйте его, вместо создания нового типа делегата.

 

События, связанные с выполняющейся программой.

 

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

Модуль стандартной библиотеки ЭтаПрограмма обеспечивает получение информации о работающей программе и её окружении, а также возможность влиять на ход её выполнения. Нас интересуют два события:

ЭтаПрограмма.ПриЗавершении
ЭтаПрограмма.ПриАварийномЗавершении

Рассмотрим их подробнее.

 

Реакция на завершение программы.

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

//Подпишемся на событие ПриЗавершении
ДобавитьОбработчик ЭтаПрограмма.ПриЗавершении, ПолучитьДелегат(,ПриЗавершенииПрограммы)
//где бы мы не вызвали эту команду, сработает событие ПриЗавершении
ЭтаПрограмма.Завершить

Создадим обработчик события ПриЗавершении:

Процедура ПриЗавершенииПрограммы(Источник Тип Объект, Параметры Тип ПараметрыСобытия)
    Консоль.Очистить
    ВыводСтроки ""
    ВыводСтроки "*** Обработали событие ПриЗавершении! ***"
КонецПроцедуры

Обратите внимание, что в Net Framework программе даётся на обработку этого события всего 2 секунды, после чего программа будет завершена, даже если обработчик события ещё не закончил свою работу. Поэтому не стоит писать в обработчике этого события большой и сложный код. 

 

Перехват не обработанного исключения.

Если исключение происходит вне оператора Попытка (или оператора Использовать у которого есть секция Исключение), то программа аварийно завершает свою работу. Однако, во время аварийного завершения программы исполнительная среда .Net предоставляет шанс выполнить какие-либо действия, что бы программа могла сохранить данные, сохранить информацию об ошибке или перезапустить себя.

Что бы воспользоваться этим шансом в языке Перфолента надо подписаться на событие ПриАварийномЗавершении объекта ЭтаПрограмма. Это можно сделать в начале работы программы с помощью оператора ДобавитьОбработчик.

Пример:

 Процедура Старт

    //Подпишемся на перехват необработанного исключения
    ДобавитьОбработчик ЭтаПрограмма.ПриАварийномЗавершении, ПолучитьДелегат(,ОбработкаАварийногоЗавершения)

    //код, в котором может произойти не обработанное исключение...

КонецПроцедуры

//обработчик события
Процедура ОбработкаАварийногоЗавершения(Источник Тип Объект, Параметры Тип ПараметрыСобытия)
    Перем Ош Тип Ошибка = ТипКТипу(Параметры.СвязанныеДанные, "Ошибка")
    ВыводСтроки "Не обработанное исключение перехваченное в событии : " + Ош.ОписаниеОшибки

    //тут можно что-то сделать для спасения данных или для перезапуска программы...

КонецПроцедуры

Обратите внимание, что в Net Framework программе даётся на обработку этого события всего 2 секунды, после чего программа будет завершена, даже если обработчик не обработанного исключения ещё не закончил свою работу. Но 2 секунды — это не мало, современный быстрый компьютер может за это время сделать довольно много, например, сохранить пользовательские данные или записать сообщение о падении в лог и перезапустить себя, если программа должна работать непрерывно.

 

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




К началу статьи


Предыдущая статья:
Конструируем класс. Делегаты.

Вернуться в раздел:
Объектно ориентированное программирование (ООП) на языке Перфолента
  Поддержи проект!

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

  Новости:
      21.01.2026 На сайт добавлена статья "Конструируем класс. События"
      20.01.2026 Опубликован новый релиз 0.4.18.0_NY языка программирования Перфолента.Net
      29.12.2025 Анонс: Новая возможность - разрабатываем веб-сайты, веб-приложения и веб-API на Перфоленте!
      07.06.2025 Небольшие дополнения к документации на сайте - описание атрибута поля &Атомарное
      09.05.2025 На сайте опубликован релиз 0.4.16.0_SE дистрибутива языка программирования Перфолента.Net
      27.04.2025 Дополнена статья про циклы
      04.01.2025 Опубликован новый релиз дистрибутива языка программирования Перфолента.Net версии 0.4.15.0_CE
      23.09.2024 Опубликована новая статья: "Конструируем класс. Делегаты."
      30.08.2024 Опубликован новый релиз дистрибутива языка программирования Перфолента.Net версии 0.4.14.0
      24.05.2024 Обновлён справочный раздел сайта
      01.07.2023 Новая версия 0.4.13.0 языка программирования Перфолента.Net
       Все новости