6. Управління подіями
У попередньому розділі ми розглянули поняття подій, пов’язаних із компонентами. Тепер ми розглянемо, як створювати події у власних класах. Це складне поняття, яке не зацікавить початківців-розробників, яким рекомендується перейти безпосередньо до наступного розділу.
6.1. Об’єкти-делегати
Інструкція
визначає тип із назвою opération, який фактично є прототипом функції, що приймає два цілі числа та повертає ціле число. Саме ключове слово delegate робить opération визначенням прототипу функції. Тип [opération] можна присвоїти змінній таким чином:
Dim op As New opération(AddressOf [fonction])
Змінна типу [délégué] створюється як об’єкт. Параметром конструктора є функція, пов’язана з екземпляром делегата. У VB ця функція задається у формі AddressOf функція, де [fonction] — це ім’я функції. Після створення екземпляра делегата його можна використовувати як функцію:
' виконується делегована функція
Dim n As Integer = op(4, 7)
Делегат [opération] було визначено як такий, що приймає два параметри типу [integer] і повертає результат того самого типу. Отже, його екземпляр [op], наведений вище, використовується як така функція. Розглянемо наступний приклад:
' делеговані функції
Imports System
Public Class delegate1
' визначення прототипу функції
' приймає 2 цілі числа як параметри та повертає ціле число
Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
' два методи екземпляра, що відповідають прототипу
' додати
Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
Return n1 + n2
End Function
' віднімати
Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
Return n1 - n2
End Function
' статичний метод, що відповідає прототипу
Public Shared Function augmenter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("augmenter(" & n1 & "," & n2 & ")"))
Return n1 + 2 * n2
End Function
' тестова програма
Public Shared Sub Main()
' визначаємо об’єкт типу «операція» для зберігання функцій
' записуємо статичну функцію «збільшити»
Dim op As New opération(AddressOf delegate1.augmenter)
' виконується делегат
Dim n As Integer = op(4, 7)
Console.Out.WriteLine(("n=" & n))
' створення об’єкта c1 типу class1
Dim c1 As New delegate1
' у делегаті реєструється метод «додати» об’єкта c1
op = New delegate1.opération(AddressOf c1.ajouter)
' виконання об’єкта-делегата
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
' у делегаті записується метод «віднімати» об’єкта c1
op = New delegate1.opération(AddressOf c1.soustraire)
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
End Sub
End Class
Результати виконання такі:
6.2. Управління подіями
Для чого може знадобитися об’єкт типу delegate?
Як ми побачимо в наступному прикладі, він служить насамперед для управління подіями. Клас C1 може генерувати події evti. Під час події evti об’єкт типу C1 запустить виконання об’єкта evtiDéclenché типу delegate. Після цього будуть виконані всі функції, зареєстровані в об’єкті-делегаті evtiDéclenché. Якщо об’єкт C2, який використовує об’єкт C1, хоче отримати сповіщення про виникнення події evti на об’єкті C1, він зареєструє один зі своїх методів C2.f у делегованому об’єкті C1.evtiDéclenché об’єкта C1, щоб метод C2.f виконувався щоразу, колиподія evti відбуватиметься на об’єкті C1. Оскільки делегований об’єкт C1.evtiDéclenché може зареєструвати кілька функцій, різні об’єкти Ci зможуть зареєструватися у делегаті C1.evtiDéclenché, щоб отримувати сповіщення про подію evti на об’єкті C1.
6.2.1. Оголошення події
Подія оголошується наступним чином (1):
Delegate Sub proc(liste d'arguments)
Public Event événement as proc
або (2)
Public Event événement(liste d'arguments)
Саме ключове слово [Event] визначає дані як подію. З подією пов'язується сигнатура, яку повинні мати її обробники. Цю сигнатуру можна визначити за допомогою делегата (метод 1) або безпосередньо (метод 2).
6.2.2. Визначення обробників події
Коли подія запускається командою [RaiseEvent], її потрібно обробити. Процедури, що відповідають за обробку подій, називаються обробниками подій. Під час створення об’єкта [Event] оголошується лише сигнатура обробників події. Ця сигнатура відповідає об’єкту [delegate], пов’язаному з подією. Після цього можна буде пов’язати з подією обробники. Це процедури, які повинні мати той самий підпис, що й [delegate], пов’язаний з подією. Щоб пов’язати обробник події з подією, використовується інструкція [AddHandler] із таким синтаксисом:
- event: змінна події, до якої прив’язується новий обробник подій
- eventhandler: ім’я обробника події — має відповідати сигнатурі [délégué], пов’язаної з цією подією
До події можна прив’язати скільки завгодно обробників. Усі вони будуть виконані, коли спрацює подія, до якої вони прив’язані.
6.2.3. Спрацьовування події
Щоб програмно викликати подію, використовується інструкція [RaiseEvent]:
- eventname: змінна події (оголошена за допомогою ключового слова Event)
- argumentlist: аргументи делегата, пов’язаного з подією.
Будуть викликані всі обробники, які були пов’язані з подією за допомогою команди AddHandler. Вони отримають як параметри ті параметри, що визначені у сигнатурі події.
6.2.4. Приклад
Розглянемо такий приклад:
'обробка подій
Imports System
Public Class myEventArgs
Inherits EventArgs
' клас події
' атрибут
Private _saisie As String
' конструктор
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' властивість, доступна тільки для читання
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Public Class émetteur
' клас, що генерує подію
' атрибут
Private _nom As String ' nom de l'émetteur
' конструктор
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' прототип функцій, що обробляють подію
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' пул обробників подій
Public Event evtHandler As _evtHandler
' метод запиту на генерацію події
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' повідомляються всі підписники
' діють так, ніби подія походить звідси
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
' клас обробки події
Public Class souscripteur
' атрибут
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' конструктор
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' записуємо ім’я підписника
Me.nom = nom
' та джерело подій
Me.sender = e
' підписуємося на отримання подій від емітента e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' управитель подій
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' відображення подій
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
' тестова програма
Public Class test
Public Shared Sub Main()
' створення генератора подій
Dim émetteur1 As New émetteur("émetteur1")
' створення масиву підписників
' для подій, що надсилаються генератором1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' зчитування послідовності цілих чисел з клавіатури
' як тільки одне з них є помилковим, генерується подія
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' поки введений рядок не є порожнім
While saisie <> ""
' чи є введене число цілим?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' це не ціле число
' повідомляємо всіх
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' повідомляємо всіх
' нове введення
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' кінець
Environment.Exit(0)
End Sub
End Class
Наведений вище код є досить складним. Розглянемо його детальніше. У системі управління подіями є генератор подій (sender), який надсилає детальну інформацію про події (EventArgs) підписникам, які заявили про свою зацікавленість у відповідних подіях. Клас генератора подій у цьому випадку такий:
Public Class émetteur
' клас, що генерує подію
' атрибут
Private _nom As String ' nom de l'émetteur
' конструктор
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' прототип функцій, що обробляють подію
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' пул обробників подій
Public Event evtHandler As _evtHandler
' метод запиту на генерацію події
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' повідомляються всі підписники
' діємо так, ніби подія походить звідси
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
Кожен об’єкт émetteur має nom, встановлений за замовчуванням. Метод ToString було перевизначено таким чином, що під час перетворення об’єкта émetteur на string, отримується саме його ім’я. Клас визначає подію:
' прототип функцій, що обробляють подію
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' подія
Public Event evtHandler As _evtHandler
Першим аргументом функції обробки події буде об’єкт, що генерує подію. Другим аргументом буде об’єкт типу myEventArgs, який надає детальну інформацію про подію; до нього ми ще повернемося. Щоб мати змогу викликати подію ззовні об’єкта émetteur, ми додаємо до класу метод envoyerEvt:
' метод запиту на генерацію події
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' повідомляємо всіх підписників
' імітується, ніби подія походить звідси
RaiseEvent evtHandler(Me, evt)
End Sub
Нам потрібно визначити, які типи подій буде запускати клас émetteur. Для цього ми визначили клас myEventArgs:
Public Class myEventArgs
' клас події
' атрибут
Private _saisie As String
' конструктор
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' властивість, доступна тільки для читання
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Нас цікавлять події, пов’язані з помилковим введенням даних з клавіатури. Ми попросимо користувача вводити цілих чисел з клавіатури, і щойно він введе рядок, який не є цілим числом, ми запустимо подію. Як деталі події ми вкажемо саме помилкове введення. Саме для цього призначений атрибут _saisie класу. Отже, об’єкт myEventArgs створюється з параметром, що містить помилкове введення. Крім того, ми перевизначаємо метод ToString, щоб під час перетворення об’єкта myEventArgs у рядок отримувався атрибут _saisie.
Ми визначили джерело подій та тип подій, які воно генерує. Далі ми визначаємо тип підписників, зацікавлених у цих подіях.
' клас обробки події
Public Class souscripteur
' атрибут
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' конструктор
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' записується ім'я підписника
Me.nom = nom
' та джерело подій
Me.sender = e
' підписуємося на отримання подій від емітента e
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' управитель подій
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' відображення подій
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
Підписник визначається двома параметрами: його ім’ям (атрибут nom) та об’єктом émetteur, події якого він хоче обробляти (атрибут sender). Ці два параметри передаються конструктору об’єкта. Під час цього ж створення підписник підписується на події джерела:
' реєструємося для отримання подій від джерела e
AddHandler e.evtHandler, AddressOf traitementEvt
Функція, зареєстрована у видавця, — traitementEvt. Цей метод класу souscripteur виводить обидва отримані аргументи (sender, evt), а також ім’я одержувача (nom). Були визначені типи подій, що генеруються, тип джерела цих подій та типи підписників. Залишилося лише реалізувати їх:
' тестова програма
Public Class test
Public Shared Sub Main()
' створення джерела подій
Dim émetteur1 As New émetteur("émetteur1")
' створення списку підписників
' для подій, що надсилаються емітентом1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' зчитування послідовності цілих чисел з клавіатури
' як тільки одне з них є помилковим, генерується подія
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' поки введений рядок не є порожнім
While saisie <> ""
' чи є введене число цілим?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' це не ціле число
' повідомляємо всіх
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' повідомляємо всіх
' нове введення
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' кінець
Environment.Exit(0)
End Sub
Створюємо об’єкт émetteur:
' створення генератора подій
Dim émetteur1 As New émetteur("émetteur1")
Ми створюємо масив із двох підписників для подій, що генеруються об’єктом émetteur1:
' створення масиву підписників
' для подій, що надсилаються генератором1
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
Ми просимо користувача ввести цілі числа з клавіатури. Як тільки введення є помилковим, ми просимо émetteur1 надіслати подію своїм підписникам:
' повідомляємо всіх
émetteur1.envoyerEvt(New myEventArgs(saisie))
Надіслана подія має тип myEventArgs і містить помилкове значення. Обидва підписники повинні отримати цю подію та повідомити про неї. Це ілюструє наведений нижче приклад виконання.
dos>evt1
Nombre entier (rien pour arrêter) : 4
Nombre entier (rien pour arrêter) : a
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s2]
Nombre entier (rien pour arrêter) : 1.6
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s2]
Nombre entier (rien pour arrêter) :