Построение
классов событий
В предыдущем
примере мы воспользовались готовым классом System.EventArgs. Возможности этого
класса весьма ограничены, поскольку его конструктор вызывается без аргументов.
При более профессиональном подходе в программе определяется новый класс события,
дополняющий этот базовый класс. Например, в него можно включить ReadOnly-свойство,
возвращающее информацию о предполагаемом повышении зарплаты, и другое свойство
для текста сообщения. Пример подобного класса приведен ниже (решение CustomEventArgExample
в архиве). Запрашиваемый рост зарплаты и сообщение инкапсулируются в конструкторе
события. В дальнейшем для получения этих данных используются два свойства, доступных
только для чтения:
Public Class ImproperSalaryRaiseEvent
Inherits System.EventArgs
Private m_Message As String
Private m_theRaise As Decimal
Sub New(ByVal
theRaise As Decimal. ByVal theReason As String)
MyBase.New()
m_Message =
theReason
m_theRaise = theRaise
End Sub
Readonly Property
Message() As String
Get
Return m_Message
End Get End
Property Readonly Property theRaise() As Decimal
Get
Return m_theRaise
End Get
End Property
End Class
После того
как этот класс будет включен в решение, следует внести небольшие изменения в
объявление события в классе Empl oyee:
Public Event SalarySecurityEvent(ByVal Sender As
CustomEventArgExample.EmployeeWithEvents. ByVale As
ImproperSalaryRaiseEvent)
Теперь во
втором аргументе передается переменная класса ImproperSalaryRai seEvent. Следующие
изменения вносятся во фрагмент, в котором непосредственно вызывается событие:
Public Overloads Sub RaiseSalary(ByVal Percent As Decimal)
If Percent >
LIMIT Then
' Операция запрещена - необходим пароль
RaiseEvent SalarySecurityEvent(Me,
New ImproperSalaryRaiseEvent(Percent, "INCORRECT PASSWORD!"))
Else
m_Salary =(1 + Percent) * m_Salary
End If
End Sub
Остается
лишь слегка исправить код обработчика события (изменения выделены жирным шрифтом).
Module Modulel
Private WithEvents
anEmployee As EmployeeWithEventsII Sub Maine)
Dim tom As New EmployeeWithEventsII("Tom". 100000)
anEmployee =
tom
Console.Wntel_ine(tom.TheName &"has salary " & tom.Salary)
anEmployee.RaiseSalary(0.2D)'Суффикс D - признак типа Decimal
Console.WriteLine(tom.TheName & "still has salary " & tom.Salary)
Console.Writeline("Please press the Enter key")
Console.ReadLine()
End Sub
Public Sub anEmployee_SalarySecuhtyEvent(ByVal Sender _ As
CustomEventArgExample.EmployeeWithEvents. ByVal e As
CustomEventArgExample.ImproperSalaryRaiseEvent) Handles
anEmployee.SalarySecurityEvent
MsgBox(Sender.TheName & "had an improper salary raise of " & _ FormatPercent(e.theRaise) & "with INCORRECT PASSWORD!")
End Sub
End Module
Результат показан на следующем рисунке. Как видно из рисунка, данные о запрошенном росте заработной платы доступны в обработчике события.
Динамическая
обработка событий
Основной
проблемой синтаксиса WithEvents является его недостаточная гибкость. Обработчики
событий нельзя динамически устанавливать и отключать на программном уровне —
фактически вся схема обработки событий жестко фиксируется в программе. Однако
в VB .NET поддерживается другой способ динамической обработки событий, значительно
более гибкий. Он основан на возможности указания процедуры класса-приемника,
вызываемой при возникновении события (исключение добавленных обработчиков также
происходит динамически).
Конечно,
для установки обработчика события необходимо зарегистрировать не только класс-приемник,
но и метод, который должен вызываться при возникновении события. Для этой цели
применяется команда AddHandler, которой при вызове передаются два параметра:
Код AddHandl
ег включается в класс-приемник, а не в класс-источник. Адрес метода, вызываемого
при возникновении события, определяется оператором AddressOf. При вызове AddressOf
передается имя метода объекта класса-приемника. Например, следующая команда
устанавливает динамический обработчик события для объекта
tom:
AddHandler tom.SalarySecurityEvent.AddressOf
anEmp1oyee_SalarySecurityEvent
В результате
тестовая программа будет обнаруживать событие Sal arySecuri tyEvent объекта
tom и в случае его возникновения — вызывать процедуру anEmployee_SalarySecurityEvent
текущего модуля (разумеется, процедура anEmployee_SalarySecurityEvent должна
обладать правильной сигнатурой!).
Ниже приведен
фрагмент решения AddHandlerExamplel (ключевые строки выделены жирным шрифтом):
Module Modulel
Private WithEvents
anEmployee As EmployeeWithEvents Sub Main()
Dim torn As New EmployeeWithEvents("Tom". 100000)
Console.WriteLine(tom.TheName & "has salary " & tom.Salary)
AddHandler tom.SalarySecurityEvent,
AddressOf anEmployee_SalarySecurityEvent
tom.RaiseSalary(0.2D) ' Суффикс D - признак типа Decimal
Console.WriteLine(tom.TheName & "still has salary " & tom.Salary)
Console.WriteLine("Please press the Enter key")
Console. ReadLine()
End Sub
Public Sub anEmployee_SalarySecurity£vent(ByVal Sender _
As AddHandlerExamplel.EmployeeWi thEvents,_
ByVal e As AddHandlerExamplel.ImproperSalaryRaiseEvent)_
Handles anEmployee.SalarySecurityEvent
MsgBox(Sender.TheName
& "had an improper salary raise of " & _
FormatPercent(e.theRaise) & "with INCORRECT PASSWORD!")
End Sub
End Module
Команда AddHandler
обладает просто невероятной гибкостью. Например, установка обработчиков событий
может зависеть от имени типа:
If TypeName(tom)="Manager"
Then
AddHandler tom.SalarySecurityEvent.AddressOf _
anEmployee_SalarySecurityEvent
e
End If
Кроме того,
один обработчик событий можно связать с несколькими разными событиями, происходящими
в разных классах. Это позволяет выполнять в VB .NET централизованную обработку
событий с динамическим назначением обработчиков — в VB такая возможность встречается
впервые. В приведенном ниже листинге инициируются разные события в зависимости
от переданных параметров командной строки. Главное место в нем занимают фрагменты
вида
Case "first"
AddHandler m_EventGenerator.TestEvent,_
AddressOf m_EventGenerator_TestEventl
При передаче
в командной строке аргумента first устанавливается соответствующий обработчик
события.
В программе
используется полезный метод GetCommandLineArgs класса System.Environment. Как
упоминалось в главе 3, этот метод возвращает массив аргументов командной строки.
Начальный элемент массива содержит имя исполняемого файла; поскольку индексация
массива начинается с 0, для получения первого аргумента используется вызов System.Environment.GetComman3LineArgs(l),
однако предварительно
необходимо убедиться в существовании аргументов командной строки, для чего проверяется
длина массива System.Environment.GetCommandLineArgs. Перед запуском программы
перейдите на страницу Configuration Properties диалогового окна Project Properties
и укажите аргументы командной строки для тестирования.
Ниже приведен
полный исходный текст программы:
Option Strict
On Module Modulel
Private m_EventGenerator As EventGenerator
Sub Main()
m_EventGenerator=
New EventGenerator()
Dim commandLinesOAs
String = System.Environment.GetCommandLineArgs
If commandLines.Length
= 1 Then
MsgBox("No command argument.program ending!")
Environment.Exit(-l)
Else
Dim theCommand As String = commandLines(l)
Console.WriteLine("Thecommand lineoption is" StheCommand)
' Проверить параметр командной строки и назначить
' соответствующий обработчик события.
Select Case theCommand
Case "first"
AddHandler m_EventGenerator.TestEvent. AddressOf
m_EventGenerator_TestEvent1
Case "second"
AddHandler m_EventGenerator.TestEvent,_ AddressOf
m_EventGenerator_TestEvent2
Case Else
AddHandler m_EventGenerator.TestEvent. AddressOf
m_EventGenerator_TestEventDefault
End Select
' Инициировать события
m_EventGenerator.TriggerEvents()
End If
Console.WriteLine("Press enter to end.")
Console. ReadLine()
End Sub
'Обработчик по умолчанию для непустой командной строки
Public Sub m_EventGenerator_TestEventDefault(_
ByVal sender
As Object.ByVal evt As EventArgs) System.Console.WriteLine("Default choice
" & _
m_EventGenerator.GetDescri
pti on()) End Sub
' Обработчик 12 для строки "first"
Public Sub m_EventGenerator_TestEvent1(_
ByVal sender As Object.ByVal evt As EventArgs)
System.Console.WriteLineC'lst
choice " & _
m_EventGenerator.GetDescription())
End Sub
'Обработчик 13 для строки "second"
Public Sub m_EventGenerator_TestEvent2(
ByVal sender As Object.ByVal evt As EventArgs)
System.Console.WriteLinet"2nd choice " & _
m_EventGenerator.GetDescri
pti on ())
End Sub
End Module
Public Class
EventGenerator
' В классе определяется
только одно событие
Public Event
TestEvent(ByVal sender As Object, ByValevt As EventArgs)
' Также можно
было использовать конструктор по умолчанию
Public Sub New()
' Пустой конструктор
End Sub
.Public Function
GetDescription() As String
Return "EventGenerator class"
End Function
' Процедура вызывается для инициирования событий
Public Sub TriggerEvents()
Dim e As System.EventArgs = New System.EventArgs()
RaiseEvent TestEvent(Me.e)
End Sub
End Class