Построение классов событий

В предыдущем примере мы воспользовались готовым классом 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