Простейшее
инициирование событий
Давайте вернемся
к простому классу Empl oyee и подробно, шаг за шагом разберем все, что необходимо
сделать для определения и инициирования событий. Предположим, событие должно
инициироваться при попытке увеличения заработной платы более чем на 10 процентов
без ввода пароля. В главе 4 метод RaiseSalary выглядел так:
Public Overloads Sub RaiseSalary(ByVal percent As Decimal)
If percent >
LIMIT Then
' Операция запрещена
- Необходим пароль
Console.WriteLine("MUST
HAVE PASSWORD TO RAISE SALARY " & _
"MORE THAN
LIMIT!!!!") Else
m_Sa1ary =(1 + percent) * m_salary
End If
End Sub
Вместо выделенной
команды, выводящей текстовое сообщение на консоль, должно инициироваться событие.
Задача решается в несколько этапов. В простейшем случае в классе сначала объявляется
открытая переменная с ключевым словом Event, с указанием имени события
и его параметров. Например, следующая строка весьма близка к синтаксису VB6:
Public Event SalarySecurityEventdnessage as String) В этой строке объявляется
открытое событие с параметром строкового типа.
События
обычно объявляются с модификатором Public, никогда не возвращают значений и
могут получать параметры произвольного типа, кроме ParamArray.
После того
как переменная события будет определена, событие инициируется командой следующего
вида (впрочем, для того, чтобы событие действительно произошло, потребуются
еще кое-какие дополнительные действия):
RaiseEvent SalarySecurityEventC'MUST HAVE PASSWORD TO RAISE " & _
"Salary
MORE THAN LIMIT!! !!")
Однако из
этого не следует, что для любого события следует ограничиваться одним строковым
параметром. В соответствии с парадигмой программирования .NET в качестве параметров
любого события передается объект-источник и информация о событии, инкапсулированная
в объекте события. На первых порах вполне достаточно объявления вида
Public Event
SalarySecurityEvent(ByVal who As Employee, ByVale As system.EventArgs)
Событие инициируется следующей командой RaiseEvent:
RaiseEvent
SalarySecurityEvent(Me,New System.EventArgs())
Хотя
события обычно объявляются открытыми, это не является обязательным требованием
— событие может иметь любой модификатор уровня доступа. Закрытыми (Private)
объявляются события, представляющие интерес только для объектов этого класса,
а защищенные (Protected) события также могут обрабатываться объектами производных
классов. Допускается даже объявление общих (Shared) событий, которые, как и
общие члены классов, существуют на уровне класса в целом, а не его отдельных
членов (в частности, общие методы могут инициировать только общие события).
По сигнатуре
события приемник узнает, от какого источника поступило событие (в данном примере
это объект-работник, которому попытались неправильно повысить заработную плату);
сам объект передается в виде ключевого слова Me. Впрочем,
приведенное объявление не использует возможностей передачи данных в переменной
события е. Вскоре мы разработаем класс, производный от System. EventArgs, в
объектах которого будет содержаться строка предупреждения вместе с данными о
попытке повышения заработной платы.
Подключение
приемников к источнику
В нашем распоряжении
имеется весь код, необходимый для рассылки событий, но пока нет ни одного заинтересованного
получателя. Существует несколько способов, которыми класс может сообщить VB
.NET о своем желании получать события от другого класса. Простейший способ очень
похож на тот, который использовался в VB6: на уровне модуля (или класса) объявляется
переменная класса-приемника с ключевым словом WithEvents. Например, если включить
в класс следующую строку, не входящую ни в один из членов: Private WithEvents
anEmployee As Employee
объекты этого
класса становятся потенциальными приемниками событий, инициируемых классом Employee.
Обратите особое внимание на некоторые особенности этого объявления:
После включения
этой строки в программу объектная переменная anEmpl oyee может использоваться
всюду, где вас интересует событие SalarySecurityEvent. Как показано на рис.
6.2, IDE автоматически создает обработчик события с именем, построенным по схеме
А_В, для каждой объектной переменной, объявленной с ключевым словом Wi thEvents.
Чтобы вызвать автоматически сгенерированный «скелет» события, достаточно
выбрать его в раскрывающемся списке, как на рис. 6.2.
А теперь
давайте объединим все сказанное на практическом примере. Создайте консольное
приложение и включите следующий фрагмент в первый (стартовый) модуль:
Module Modulel
Private WithEvents
anEmployee As EmployeeWithEvents
Sub Main()
Dim tom As New EmployeeWithEvents("Tom". 100000)
anEmployee =
tom
Console.WriteLine(tom.TheName & "has salary " & tom.Salary)
anEmployee.RaiseSalary(0.2D) ' Суффикс D - признак типа Decimal
Console.WriteLinettom.TheName & "still has salary " & tom.Salary)
Console.WritelineC'Please press the Enter key")
Console.ReadLine()
End Sub End Module
Рис.
6.2. Автоматически сгенерированный код обработчика события
Теперь выберите
в раскрывающемся списке метод anEmployee_SalarySecurityEvent. Исходный текст
этого метода приведен ниже (для удобства чтения он разбит на несколько строк,
а ключевая секция Handles выделена жирным шрифтом):
Public Sub anEmployee_SalarySecur1tyEvent(ByVal
Sender As
Event_Handling_I.EmployeeWithEvents, ByValeAs System.EventArgs) Handles
anEmployee.SalarySecurityEverrt
End Sub
End Module
Обратите
внимание на символ подчеркивания, добавленный VB .NET между именем переменной
с ключевым словом WithEvents (anEmployee) и именем события (SalarySecurityEvent),
— с ним обработчик внешне почти не отличается от процедур событий в VB6.
Также обратите
внимание на идентификацию объекта Sender полным именем (в формате пространство_имен.
имя_класса). Наличие дополнительных символов подчеркивания в пространстве имен
объясняется тем, что пробелы в них не разрешены, поэтому VB .NET автоматически
преобразует имя решения «Event Handling 1» в «Event_Handling_l»
(рис. 6.3). Наконец, ключевое слово Handles сообщает исполнительной среде, какое
событие обрабатывается этой процедурой.
Рис.
6.3. Окно свойств решения с обработкой событий
Чтобы пример
стал более интересным, вместо простого вывода в консольное окно 'мы включим
в процедуру события команду вызова диалогового окна:
Public Sub anEmployee_SalarySecurityEvent(ByVal
Sender As
Event_Handling_I.EmployeeWithEvents.
ByVal e As System.EventArgs)
Handles anEmployee.SalarySecurityEvent
MsgBox(Sender.TheName
&"had an improper salary raise attempted!")
End Sub
От приемника
событий мы переходим к источнику. В класс Employee из главы 4 необходимо внести
два изменения, выделенные в следующем листинге жирным шрифтом:
Public Class EmployeeWithEvents
Private m_Name As String
Private m_Salary As Decimal
Private Const LIMIT As Decimal =0.1D
Public Event SalarySecurityEventCByVal Sender As
EmployeeWithEvents,ByVal e As EventArgs)
Public Sub NewCByVal
aName As String.
ByVal curSalary As Decimal)
m_Name = aName
m_Salary = curSalary
End Sub Readonly
Property TheName() As String
Get
Return m_Name
End Get
End Property
Readonly Property Salary() As Decimal s,
Get
Return m_Salary
End Get ' '
End Property
Public Overloads Sub RaiseSalary(ByVal Percent As Decimal)
If Percent >
LIMIT'Then
'Операция запрещена - необходим пароль
RaiseEvent SalarySecurityEventtMe, New System.EventArgs())
Else
m_Sa1ary = (1 + Percent) * m_Salary
End If
End Sub
Public Overloads
Sub RaiseSalary(ByVal Percent As Decimal.
ByVal Password As String)
If Password
= "special" Then
m_Salary = (1 + Percent) * m_Salary
End If
End Sub
End Class
Первый выделенный
фрагмент объявляет событие, а второй инициирует его при попытке недопустимого
повышения зарплаты.
Примерный
результат запуска программы показан на рис. 6.4. При нажатии кнопки ОК окно
сообщения исчезает, и в консольном окне выводится строка, из которой видно,
что зарплата Тома не изменилась.
Переменные WithEvents потребляют системные ресурсы. Как только такая перемен-ная становится ненужной, присвойте ей Nothing.
Рис.
6.4. Окно сообщения, вызываемое при обработке события