Обработка событий и делегаты

  • Обработка событий с точки зрения ООП
  • Передача данных функциям, вызываемым в результате событий
  • Простейшее инициирование событий
  • Подключение приемников к источнику
  • Все вместе
  • Построение классов событий
  • Динамическая обработка событий
  • Отключение обработчиков событий
  • Обработка событий в иерархии наследования
  • Делегаты
  • Создание делегата
  • Практический пример: специализированная сортировка
  • Групповые делегаты
  • Групповые делегаты как члены классов
  • Делегаты и события
  • Предыдущие версии Visual Basic убедительно показали, что модель программирования, управляемая событиями и основанная на применении объектов, повышает производительность труда программиста. Стоило вам перетащить элемент на форму, как он начинал реагировать на определенные события. Например, код процедуры события Button1_Click автоматически выполнялся при нажатии кнопки с именем Button1.

    Но, несмотря на эффективность, модель, использованная в прежних версиях VB, была недостаточно гибкой. В частности, в ней было трудно определять новые события, а написать обработчик, который обрабатывает сразу несколько событий, было практически невозможно. В VB .NET удобство и эффективность объединились с богатством возможностей. Обычно используется синтаксис, очень близкий к синтаксису прежних версий VB, при этом VB .NET берет на себя всю «черную работу». Если понадобится сделать что-то нестандартное — VB .NET предоставит и такую возможность. Глава начинается с описания модели обработки событий, похожей на аналогичную модель из предыдущих версий VB (хотя и гораздо более мощной). Далее мы представим новую для VB концепцию делегатов и покажем, как с их помощью в полной мере использовать возможности платформы .NET по обработке событий, а также решать более общие задачи (например, организовать обратный вызов функций).

     

    Обработка событий с точки зрения ООП

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

    Но при этом возникает очевидная проблема: каким объектам следует отправлять сообщения? Оповещать о каждом событии все объекты, в настоящий момент существующие в программе? Это было бы слишком неэффективно. Для большийства объектов событие не представляет ни малейшего интереса, а быстродействие станет неприемлемо низким.

    Вместо этого VB .NET пытается ограничить число получателей события, для чего используется модель «подписка/публикация». В этой модели объекты-приемники событий регистрируют объекты-источники тех событий, которые представляют для них интерес. На события от одного источника могут подписаться сразу несколько объектов-приемников. О том, что источник инициировал событие, оповещаются только зарегистрированные получатели.

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

    Общий смысл происходящего заключается в том, что при возникновении события объект-источник вызывает заранее определенные функции объектов-приемников. Вызываемая функция приемника регистрируется источником события одновременно с регистрацией объекта-приемника. Такая схема называется оповещением посредством обратного вызова (callback notification), потому что источник события вызывает метод приемника по заранее известному ему адресу. На рис. 6.1 показан объект-«начальник» с событием HighRating, при возникновении вызываются разные методы объектов-приемников. Во второй половине этой главы будет рассказано, как это происходит в VB .NET.

    Чтобы регистрация прошла успешно, методы объекта-приемника должны иметь строго определенную форму (например, обладать конкретной сигнатурой). По этой причине в некоторых языках (например, в Java) механизм обратного вызова реализуется с применением интерфейсов — это гарантирует наличие метода с правильной сигнатурой.

    Рис. 6.1. Схема оповещения посредством обратного вызова

     

    Передача данных функциям, вызываемым в результате событий

    Конечно, вы можете определить собственную сигнатуру для методов объекта-приемника, вызываемых источником, однако в .NET существует практически общепринятое правило, согласно которому функции приемника передаются два параметра:

  • Объектная переменная, содержащая ссылку на объект-источник события.
  • Объект события (класса, производного от System.EventArgs), содержащий информацию о событии (разные классы, производные от System.Event.Args, обладают разными свойствами, ориентированными на разные обработчики событий).
  • Пример приводился ранее в главе 1. При размещении кнопки на форме генерировалась процедура события Click:

    Private Sub Buttonl_Click(ByVal sender As System.Object.

    ByValeAs System.EventArgs) Handles Button1.Click

    End Sub

    Параметры имеют следующий смысл:

  • Объектная переменная sender содержит ссылку на объект, то есть кнопку, нажатую пользователем. Следовательно, процедура события располагает информацией об источнике события.
  • Объектная переменная е содержит объект события, который (по крайней мере теоретически) содержит дополнительную информацию о событии.
  • Традиционно в VB источник (отправитель) события не идентифицировался в процедуре события. Единственным исключением были массивы управляющих элементов, когда конкретный элемент-отправитель выделялся из массива при помощи параметра-индекса. Смысл дополнительной объектной переменной sender в обобщенной процедуре события VB .NET становится очевидным, если вспомнить, что одна процедура может обрабатывать несколько событий, поступающих от разных объектов. Попробуйте вызвать встроенный метод ToString в приведенной выше процедуре события: MsgBox(sender.ToString) Результат будет выглядеть так:

    Таким образом, процедура-обработчик может однозначно определить, какой объект был источником события.

    В данном примере объект события е не представляет интереса, поскольку он не содержит сколько-нибудь полезной информации о событии. С другой стороны, в некоторых ситуациях он может пригодиться. Например, из объектной переменной класса MouseEventArgs можно узнать, в какой точке был сделан щелчок мышью. В общем случае программист определяет собственные классы событий, производные от класса System. EventArgs и содержащие любую полезную информацию о событии (о том, как это делается, будет рассказано ниже).

    Также обратите внимание на новое ключевое слово Hand! es в определении процедуры события. Как нетрудно догадаться, это ключевое слово указывает, какие события обрабатываются данной процедурой. Возможно, в данном примере ключевое слово Handl es выглядит излишним, однако оно предоставляет программисту дополнительные возможности, поскольку теперь обработчики события не обязаны обладать жестко заданными именами (фиксируются только сигнатуры). Следовательно, одна процедура может обрабатывать несколько событий, для чего в конец объявления процедуры включаются несколько секций Handl es. Новый подход обладает большей гибкостью по сравнению с массивами управляющих элементов, использовавшимися в прежних версиях VB (в VB .NET массивы элементов не поддерживаются).

    Хотя IDE генерирует процедуры событий со стандартными именами, в VB .NET это уже не является обязательным требованием. Если процедура имеет правильный набор параметров и в ее заголовке присутствует ключевое слово Handles, эта процедура может использоваться для обработки событий. Пример:

    Private Sub MyClickProcedure(ByVal sender As System.Object,_

    ByValeAs System.EventArgs) Handles Buttonl.Click

    Процедура MyClickProcedure может обрабатывать событие Buttonl. Click благодаря наличию правильных параметров. Она обрабатывает это событие, поскольку в заголовке указано ключевое слово Handles. Главное новшество заключается в явном указании обрабатываемых событий с ключевым словом Handles.

    Рассмотрим другой пример. Допустим, предыдущий фрагмент был приведен к следующему виду:

    Private Sub MyClickProcedureCByVal sender As System.Object._

    ByVal e As System.EventArgs) Handles Buttonl.Click. Button2.Click._

    mnuTHing.Click

    Теперь одна процедура обрабатывает события сразу от двух разных кнопок и команды меню! В VB6 подобная универсальность была невозможна, поскольку в прежних версиях VB обработчики событий вызывались по имени элемента. Надеемся, читатель согласится с тем, что ключевое слово Handl es обладает значительно большим потенциалом, чем массивы управляющих элементов.