Тверской государственный университет
Опубликован: 13.09.2006 | Доступ: свободный | Студентов: 3491 / 369 | Оценка: 4.65 / 4.29 | Длительность: 30:37:00
Специальности: Программист, Менеджер
Лекция 5:

Классы и объекты

< Лекция 4 || Лекция 5: 123456 || Лекция 6 >

События собственных классов

Возможность создавать события собственных классов - это новинка Office 2000. Ранее было возможным для классов иметь только два стандартных события - Initialize и Terminate, играющих роль конструктора и деструктора по умолчанию. Теперь можно в классе создать набор событий и для каждого из экземпляров класса написать обработчики любого из этих событий. Тем самым расширяются возможности созданных Вами объектов. Это существенное продвижение в сторону классики объектного программирования. Попробуем разобраться:

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

Первый вопрос, который необходимо решить при создании класса с событиями, лежит вне программирования, прежде всего, нужно решить, каким должен быть набор событий у объектов класса. Если с объектом связан некоторый графический образ, то ситуация более или менее стандартна, всегда разумно иметь события Click, Move, Resize и другие, типичные для графических объектов. Эти события будут возникать, когда пользователь будет работать с графическим образом, выполняя над ним те или иные типичные действия. Во многих случаях, наши объекты могут и не иметь графического образа, но и для них не менее полезно определить события. Общие рекомендации давать трудно, поэтому обратимся к примеру и рассмотрим ранее созданный класс Личность. Какие события разумно было бы определить для объектов этого класса? Учитывая, что свойств у объектов класса не так много, число разумных событий также не велико. Давайте определим два события - ИзменениеФамилии и ДеньРождения. Первое из этих событий возникает в ответ на изменение значения свойства Фамилия, второе, - когда текущая дата отличается от даты дня рождения Личности не более чем на сутки.

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

Option Explicit
'Класс Личность
'События класса
Public Event ИзменениеФамилии(Fam As String, NewFam As String)
Public Event ДеньРождения(Dat As Date)

'Далее следует уже знакомое определение свойств и методов класса
'Смотри раздел "Создание класса Личность" этой лекции

Итак, чтобы создать класс с событиями просто добавьте список этих событий в объявление класса!

Как зажигаются события

Чтобы событие, связанное с объектом возникло, его нужно зажечь или возбудить ( Raise ). Зажигается событие специальной процедурой RaiseEvent, имеющей следующий синтаксис:

Sub RaiseEvent имя_события (параметры)

Эта процедура не только возбуждает событие, но и передает некоторые параметры, которые могут быть использованы в обработчике события. Что стоит за терминами "зажечь" ("возбудить") событие? При выполнении этой процедуры операционной системе посылается уведомление о том, что та должна послать объекту сообщение о возникновении события с указанным именем, в ответ на получение которого будет вызван обработчик этого события. Так что при выполнении этой процедуры плавный ход выполнения программы прерывается, происходит обмен сообщениями, вызов обработчика события и только по его завершении будет продолжено выполнение операторов, стоящих после вызова RaiseEvent.

Процедура RaiseEvent должна выполняться в одном из методов класса с событиями. Правильное определение ее места, это также одна из задач, которую нужно решить при проектировании класса, возможно, придется создавать специальный метод для этой цели. Обратимся к нашему примеру, когда и где нужно зажечь событие ИзменениеФамилии. Здесь ответ очевиден, - всякий раз, когда у объекта изменяется значение свойства Фамилия, должно возникать это событие. Но изменять значение для Private свойства следует единственным образом, - вызовом Property Let ВашаФамилия. Поэтому эта процедура именно то место, где должно возбуждаться событие. А в процедуре разумно поставить вызов RaiseEvent непосредственно перед оператором, изменяющим фамилию. Вот как выглядит эта процедура:

Public Property Let ВашаФамилия(ByVal NewValue As String)
    'Зажигает событие ИзменениеФамилии
    RaiseEvent ИзменениеФамилии(Фамилия, NewValue)
    Фамилия = NewValue
End Property

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

Public Event ИзменениеФамилии(Fam As String, NewFam As String, _
            Permission As Boolean)

Соответственно изменится и процедура - свойство Property Let. Вот другой, более разумный вариант этой процедуры:

Public Property Let ВашаФамилия(ByVal NewValue As String)
    'Зажигает событие ИзменениеФамилии
    Dim Permission As Boolean 'Разрешение на изменение фамилии
    Permission = True
    RaiseEvent ИзменениеФамилии(Фамилия, NewValue, Permission)
    'Обработчик события может запретить изменение фамилии
    If Permission Then Фамилия = NewValue
End Property

Менее ясно, где и когда следует зажигать событие ДеньРождения. Мы решили возбуждать это событие в тот момент, когда пользователь интересуется датой рождения объекта, и вставили соответствующую проверку в Property Get ВашаДатаРождения процедуру:

Public Property Get ВашаДатаРождения() As Date
    'Зажигает событие ДеньРождения
    'в зависимости от значения текущей даты
    If (Month(Now) = Month(ДатаРождения)) And _
        (Abs(Day(Now) - Day(ДатаРождения)) <= 1) Then
        RaiseEvent ДеньРождения(ДатаРождения)
    End If
    ВашаДатаРождения = ДатаРождения
End Property

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

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

Где и как следует создавать обработчики событий для экземпляров класса

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

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

Модуль, в котором можно объявлять объекты With Events может быть либо модулем класса, либо модулем формы, но, заметьте, это не может быть стандартный модуль. В большинстве случаев предпочтение отдается модулю класса. Позже мы остановимся подробнее на ситуации, когда объекты с событиями объявляются в модуле формы. Предположим, что для решения некоторой задачи, нам потребовалось работать с двумя конкретными объектами класса Личность, - нашим старым приятелем и знакомой молодой девушкой. Для решения задачи создадим класс, который назовем " Личности " и поместим туда объявление двух объектов MyFriendOne и MyFriendTwo. Объекты объявлены со спецификатором WithEvents, указывающим на то, что они должны реагировать на события. Как только эти объявления появятся в разделе объявлений класса, сами объекты появятся в окне кода в списке объектов. Теперь, выбрав объект из списка, можно открыть список возможных событий объекта и создать вначале заготовку обработчика событий, а потом наполнить ее содержанием.

Создание обработчиков событий

увеличить изображение
Рис. 4.5. Создание обработчиков событий

Заметьте, эта технология работы нам уже хорошо знакома. Она использовалась при создании объектов Application With Events. Теперь становится понятным, что тогда шла речь о частном случае. Помните, при создании класса ExcelWithEvents, говоря о его структуре, мы обращали внимание на то, что в классе объявлен всего лишь один объект с событиями. Теперь становится понятным, это отражало специфику частного случая, - приложение, обычно, существует в единственном экземпляре. Личностей же, реагирующих на события, может быть много, - для каждой из них нужно задать свой экземпляр объекта в классе, чтобы была возможность написать индивидуальный обработчик события. Взгляните на реализацию класса Личности:

Option Explicit
Private WithEvents myFriendOne As Личность
Private WithEvents myFriendTwo As Личность

Private Sub myFriendOne_ДеньРождения(Dat As Date)
    BirthDay (Dat)
End Sub

Private Sub myFriendOne_ИзменениеФамилии(Fam As String, _
    NewFam As String, Permission As Boolean)
    MsgBox ("Изменение фамилии " & Fam & " на " & NewFam & Chr(13) _
        & "не разрешается.")
  Permission = False
End Sub

Private Sub myFriendTwo_ДеньРождения(Dat As Date)
    BirthDay (Dat)
End Sub

Private Sub myFriendTwo_ИзменениеФамилии(Fam As String, _
    NewFam As String, Permission As Boolean)
    MsgBox ("Поздравляю с замужеством, дорогая " & _
      Fam & "-" & NewFam & "!")
    Permission = True
End Sub

Public Sub BirthDay(Dat As Date)
Debug.Print Dat, "-", Now
 Select Case Day(Dat)
        Case Day(Now)
            MsgBox ("Сегодня День Рождения!")
        Case Is < Day(Now)
            MsgBox ("Вчера был День Рождения!")
        Case Else
            MsgBox ("Завтра День Рождения!")
    End Select

End Sub 

Public Sub Connect()
    Set myFriendOne = FriendOne
    Set myFriendTwo = FriendTwo
End Sub
4.6.

Как видите, в этом классе два Private объекта, реагирующих на события, и для каждого из них написаны по два обработчика событий класса. Сейчас не суть важно, что делают эти обработчики. В одном случае обработчик события ИзменениеФамилии разрешает это изменение, в другом - нет, и выдает по этому поводу, естественно, разные сообщения. Обработчики события ДеньРождения выполняют одну и ту же задачу, поэтому они вызывают общую процедуру работы. Это, хотя и интересные, но все-таки детали. Более важную для понимания играет роль процедура, названная Connect.

Связывание объектов

Особенностью рассматриваемой технологии работы с объектами, реагирующими на события, является то, что существует пара объектов - двойников. Один из них, реальный объект, объявляется как объект без событий, другой, его двойник, объявляется в специальном классе с описателем With Events. Для двойника создаются обработчики событий, но, чтобы все это заработало нужным образом, эту пару объектов нужно связать законными узами брака. Так мы поступали при работе с объектами Excel.Application и Excel.Application WithEvents. Также мы поступили в процедуре Connect с двумя парами объектов - двойников: MyFriendOne - FriendOne и MyFriendTwo - FriendTwo.

Реальные объекты и инициирование событий

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

Для класса Личность мы уже описали методы, зажигающие события. Поэтому нам осталось только привести описание стандартного модуля Примеры, в котором ведется работа с двумя объектами, названными FriendOne и FriendTwo:

Option Explicit
'Модуль Примеры
Public FriendOne As New Личность
Public FriendTwo As New Личность
Public FOne As New Личности

Public Sub Знакомство()
'Вызывается конструктор с параметрами
'и происходит знакомство с объектами
FriendOne.InitPerson FN:="Станислав", LN:="Федотов", _
    DOB:="21.05.39"
FriendTwo.InitPerson FN:="Катя", LN:="Павлова", _
    DOB:="22.03.79"
FriendOne.PrintPerson
FriendTwo.PrintPerson
FriendOne.SayWhoIs
FriendTwo.SayWhoIs
'Связывание с двойниками.
'Теперь объекты могут реагировать на события!
FOne.Connect
End Sub

Public Sub CallEvents()
    Dim DOB As Date
   
 'Вызов методов приведет к возникновению событий!
    'При замене фамилии возникнет событие ИзменениеФамилии
    'Заметьте, не всегда фамилия будет изменена!
    FriendOne.ВашаФамилия = "Фидотов"
    FriendTwo.ВашаФамилия = "Волконская"

    'При попытке узнать дату рождения
    'может быть вызван обработчик события ДеньРождения.
    DOB = FriendOne.ВашаДатаРождения
    DOB = FriendTwo.ВашаДатаРождения
   FriendOne.PrintPerson
   FriendTwo.PrintPerson
End Sub
4.7.

В этом модуле объявлены два объекта класса Личность и один объект класса Личности. Две процедуры - Знакомство и CallEvents вызываются в ответ на щелчки командных кнопок в соответствующих обработчиках событий. Первой должна вызываться процедура Знакомство, где объекты FriendOne и FriendTwo получают "нормальные" значения и происходит Знакомство с ними. Важно, что здесь же происходит связывание этих объектов с их двойниками. Обратите внимание, нам понадобился объект FOne класса Личности, чтобы вызвать его метод Connect и связать эти пары объектов. В процессе работы этих модулей будет вестись диалог с пользователем. На следующих рисунках показаны сообщения, выдаваемые обработчиками событий ИзменениеФамилии и ДеньРождения.

Обработчик события ИзменениеФамилии объекта One

Рис. 4.6. Обработчик события ИзменениеФамилии объекта One
Обработчик события ИзменениеФамилии объекта Two

Рис. 4.7. Обработчик события ИзменениеФамилии объекта Two
Обработчик события ДеньРождения объекта Two

Рис. 4.8. Обработчик события ДеньРождения объекта Two

Приведем еще результаты отладочной печати:

Станислав             Федотов       родился       21.05.39 
Катя                  Павлова       родилась      22.03.79 
22.03.79      -       22.03.99 13:14:01 
Станислав             Федотов       родился       21.05.39 
Катя                  Волконская    родилась      22.03.79
Итоги

В заключение, коротко опишем всю картину создания класса и работы с объектами, реагирующими на события. Прежде всего, нужно создать сам класс объектов, в котором предусмотрен набор событий, возможных для объектов класса. В методах класса следует предусмотреть возможность возбуждения событий. На следующем этапе следует создать класс, в котором объявлены объекты WithEvents. В этом классе создаются обработчики событий для каждого экземпляра класса. Кроме этих объектов - двойников создаются и "реальные" объекты, как правило, в стандартном модуле. После того, как происходит связывание двойников, объекты могут реагировать на события. Всякий раз, когда объект вызывает метод класса, в котором возбуждается событие, это приводит к тому, что вызывается обработчик события. Можно передавать и получать информацию от обработчика событий.

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

< Лекция 4 || Лекция 5: 123456 || Лекция 6 >
полина есенкова
полина есенкова
Дмитрий Вологжин
Дмитрий Вологжин
Добрый день, прошел тесты с 1 по 9, 10 не сдал, стал читать лекцию и всё пройденные тесты с 1 по 9 сбросились, когда захотел пересдать 10 тест.