Событийная процедура
представляет собой подпрограмму, которая начинает выполняться после реализации определенного события.
Имя процедуры включает в себя имя объекта и имя события:
Объект_Событие ()
Каждая процедура представляет отдельный программный модуль, который реализует определенный алгоритм. В терминологии процедурного программирования такие процедуры соответствуют подпрограммам, поэтому каждая из событийных процедур начинается с ключевого слова Sub (subroutine — подпрограмма) и заканчивается ключевыми словами End Sub
Что такое событийная процедура
3. Графический интерфейс проекта и событийные процедуры.
Графический интерфейс проекта. Графический интерфейс необходим для реализации интерактивного диалога пользователя с запущенным на выполнение готовым проектом. Основой для создания графического интерфейса разрабатываемого проекта является объект «форма». Форма представляет собой окно, на котором размещаются другие объекты — элементы управления.
Элементы управления имеют различное назначение. Текстовые поля ( TextBox ) используются для ввода и вывода данных, надписи ( Label ) — для вывода данных и пояснительных текстов, графические окна ( PictureBox ) — для вывода графики, кнопки ( Button ) — для запуска событийных процедур (рис. 2.6).
Рис. 2.6. Элементы
управления на форме
Графический интерфейс проекта представляет собой форму, на которой размещены управляющие элементы.
Визуальное конструирование графического интерфейса проекта состоит в том, что на форме с помощью мыши «рисуются» те или иные элементы управления. Выбрав щелчком мышью нужный элемент на Панели объектов, мы можем поместить его на форму разрабатываемого проекта. Процесс размещения на форме элементов управления аналогичен рисованию графических примитивов с использованием графического редактора.
Событийные процедуры. Событие ( Event ) представляет собой действие, распознаваемое элементом управления. Событие может создаваться пользователем (например, щелчок мышью или нажатие клавиши) или быть результатом воздействия других программных объектов.
Каждый объект реагирует на определенный набор событий. Например, кнопка реагирует на щелчок ( Click ), нажатие ( MouseDown ) и отпускание ( MouseUp ) мыши или нажатие определенной клавиши на клавиатуре ( KeyPress ).
Для каждого события можно запрограммировать отклик, т. е. реакцию объекта на произошедшее событие. Если пользователь производит какое-либо воздействие на элемент графического интерфейса (например, щелчок), в качестве отклика выполняется событийная процедура, представляющая собой программу.
Для того чтобы создать заготовку событийной процедуры, необходимо в режиме разработки проекта осуществить двойной щелчок мышью по объекту. Например, после щелчка по кнопке Button 1 в окне Программный код будет создана заготовка событийной процедуры:
Private Sub Button1_Click (. )
Служебные слова Private Sub и End Sub обозначают начало и конец событийной процедуры. Имя событийной процедуры Button 1_ Click (. ) включает в себя имя объекта и имя события.
Далее необходимо ввести в событийную процедуру программный код, который реализует определенный алгоритм.
Событийная процедура представляет собой программу, которая начинает выполняться после реализации определенного события.
Проект «Первый проект» . Создать проект, в котором в первой событийной процедуре свойству Text всех объектов будут присвоены новые значения, а во второй событийной процедуре будут изменены размеры формы с помощью метода Scale () , очищено текстовое поле с помощью метода Clear () и скрыто графическое поле с помощью метода Hide () .
Создадим графический интерфейс проекта (рис. 2.7).
1. Поместить на форму две кнопки Button 1 и Button 2 , текстовое поле TextBox 1 , надпись Label 1 и графическое поле PictureBox 1 .
2. Последовательно выделить объекты PictureBox 1 и Label 1 и с помощью диалогового окна Свойства установить для свойства BackColor (цвет фона) значение White (белый).
Создадим событийные процедуры. Последовательно осуществим двойной щелчок по обеим кнопкам и внесем в заготовки событийных процедур программные коды.
3. Private Sub Button1_Click (. )
Me .Text font-size:10.0pt;font-family:Courier New»>Первый проект «
TextBox1.Text font-size:10.0pt;font-family:Courier New»>Текстовое поле «
Label1.Text font-size:10.0pt;font-family:Courier New»>Надпись «
Button1.Text font-size:10.0pt;font-family:Courier New»>Свойства «
Button2.Text font-size:10.0pt;font-family:Courier New»>Методы «
Private Sub Button2_Click (. )
Me .Scale(2, 0.5)
End Sub
4. После запуска проекта на выполнение и щелчков по кнопкам событийные процедуры будут выполнены (см. рис. 2.7).
События .NET в деталях
Событием в языке C# называется сущность, предоставляющая две возможности: для класса — сообщать об изменениях, а для его пользователей — реагировать на них.
Пример объявления события:
Рассмотрим, из чего состоит объявление. Сначала идут модификаторы события, затем ключевое слово event, после него — тип события, который обязательно должен быть типом-делегатом, и идентификатор события, то есть его имя. Ключевое слово event сообщает компилятору о том, что это не публичное поле, а специальным образом раскрывающаяся конструкция, скрывающая от программиста детали реализации механизма событий. Для того, чтобы понять, как работает этот механизм, необходимо изучить принципы работы делегатов.
Основа работы событий — делегаты
Можно сказать, что делегат в .NET — некий аналог ссылки на функцию в C++. Вместе с тем, такое определение неточно, т.к. каждый делегат может ссылаться не на один, а на произвольное количество методов, которые хранятся в списке вызовов делегата (invocation list). Тип делегата описывает сигнатуру метода, на который он может ссылаться, экземпляры этого типа имеют свои методы, свойства и операторы. При вызове метода Invoke() выполняется последовательный вызов каждого из методов списка. Делегат можно вызывать как функцию, компилятор транслирует такой вызов в вызов Invoke().
В C# для делегатов имеются операторы + и -, которые не существуют в среде .NET и являются синтаксическим сахаром языка, раскрываясь в вызов методов Delegate.Combine и Delegate.Remove соответственно. Эти методы позволяют добавлять и удалять методы в списке вызовов. Разумеется, форма операторов с присваиванием (+= и -=) также применима к операторам делегата, как и к определенным в среде .NET операторам + и — для других типов. Если при вычитании из делегата его список вызовов оказывается пуст, то ему присваивается null.
Рассмотрим простой пример:
События — реализация по умолчанию
- Неявная реализация события (field-like event).
- Явная реализация события.
Рассмотрим наиболее часто используемую реализацию событий — неявную. Пусть имеется следующий исходный код на языке C# 4 (это важно, для более ранних версий генерируется несколько иной код, о чем будет рассказано далее):
Эти строчки будут транслированы компилятором в код, аналогичный следующему:
Блок add вызывается при подписке на событие, блок remove — при отписке. Эти блоки компилируются в отдельные методы с уникальными именами. Оба этих метода принимают один параметр — делегат типа, соответствующего типу события и не имеют возвращаемого значения. Имя параметра всегда ”value”, попытка объявить локальную переменную с таким именем приведет к ошибке компиляции. Область видимости, указанная слева от ключевого слова event определяет область видимости этих методов. Также создается делегат с именем события, который всегда приватный. Именно поэтому мы не можем вызвать событие, реализованное неявным способом, из наследника класса.
Interlocked.CompareExchange выполняет сравнение первого аргумента с третьим и если они равны, заменяет первый аргумент на второй. Это действие потокобезопасно. Цикл используется для случая, когда после присвоения переменной comparand делегата события и до выполнения сравнения другой поток изменяет этот делегат. В таком случае Interlocked.CompareExchange не производит замены, граничное условие цикла не выполняется и происходит следующая попытка.
Объявление с указанием add и remove
При явной реализации события программист объявляет делегат-поле для события и вручную добавляет или удаляет подписчиков через блоки add/remove, оба из которых должны присутствовать. Такое объявление часто используется для создания своего механизма событий с сохранением удобств языка C# в работе с ними.
Например, одна из типичных реализаций заключается в отдельном хранении словаря делегатов событий, в котором присутствуют только те делегаты, на события которых была осуществлена подписка. Доступ к словарю осуществляется по ключам, которыми обычно являются статические поля типа object, используемые только для сравнения их ссылок. Это делается для того, чтобы уменьшить количество памяти, занимаемое экземпляром класса (в случае, если он содержит большое количество нестатических событий). Эта реализация применяется в WinForms.
Как происходит подписка на событие и его вызов?
Все действия по подписке и отписке (обозначаются как += и -=, можно легко спутать с операторами делегатов) компилируются в вызовы методов add и remove. Вызовы внутри класса, отличные от вышеуказанных, компилируются в простую работу с делегатом. Следует заметить, что при неявной (и при правильной явной) реализации события невозможно получить доступ к делегату извне класса, работать можно лишь с событием как с абстракцией — подписываясь и отписываясь. Так как нет способа определить, подписались ли мы на какое-либо событие (если не использовать рефлексию), то кажется логичным, что отписка от него никогда не вызовет ошибок — можно смело отписываться, даже если делегат события пуст.
Модификаторы событий
Для событий могут использоваться модификаторы области видимости (public, protected, private, internal), они могут быть перекрыты (virtual, override, sealed) или не реализованы (abstract, extern). Событие может перекрывать событие с таким же именем из базового класса (new) или быть членом класса (static). Если событие объявлено и с модификатором override и с модификатором abstract одновременно, то наследники класса должны будут переопределить его (равно как и методы или свойства с этими двумя модификаторами).
Какие типы событий бывают?
Как уже было отмечено, тип события всегда должен быть типом делегата. Стандартными типами для событий являются типы EventHandler и EventHandler<TEventArgs> где TEventArgs — наследник EventArgs. Тип EventHandler используется когда аргументов события не предусмотрено, а тип EventHandler<TEventArgs> — когда аргументы события есть, тогда для них создается отдельный класс — наследник от EventArgs. Также можно использовать любые другие типы делегатов, но применение типизированного EventHandler<TEventArgs> выглядит более логичным и красивым.
Как все обстоит в C# 3?
Реализация field-like события, которая описана выше, соответствует языку C# 4 (.NET 4.0). Для более ранних версий существуют весьма существенные отличия.
Неявная реализация использует lock(this) для обеспечения потокобезопасности вместо Interlocked.CompareExchange с циклом. Для статических событий используется lock(typeof(Class)). Вот код, аналогичный раскрытому компилятором неявному определению события в C# 3:
Помимо этого, работа с событием внутри класса ведется как с делегатом, т.е. += и -= вызывают Delegate.Combine и Delegate.Remove напрямую, в обход методов add/remove. Это изменение может привести к невозможности сборки проекта на языке C# 4! В C# 3 результатом += и -= был делегат, т.к. результатом присвоения переменной всегда является присвоенное значение. В C# 4 результатом является void, т.к. методы add/remove не возвращают значения.
Помимо изменений в работе на разных версиях языка есть еще несколько особенностей.
Особенность №1 — продление времени жизни подписчика
При подписке на событие мы добавляем в список вызовов делегата события ссылку на метод, который будет вызван при вызове события. Таким образом, память, занимаемая объектом, подписавшимся на событие, не будет освобождена до его отписки от события или до уничтожения объекта, заключающего в себе событие. Эта особенность является одной из часто встречаемых причин утечек памяти в приложениях.
Для исправления этого недостатка часто используются weak events, слабые события. Эта тема уже была освещена на Хабре.
Особенность №2 — явная реализация интерфейса
Событие, являющееся частью интерфейса, не может быть реализовано как поле при явной реализации этого интерфейса. В таких случаях следует либо скопировать стандартную реализацию события для реализации как свойство, либо реализовывать эту часть интерфейса неявно. Также, если вам не нужна потокобезопасность этого события, можно использовать самое простое и эффективное определение:
Особенность №3 — безопасный вызов
События перед вызовом следует проверять на null, что следует из описанной выше работы делегатов. От этого разрастается код, для избежания чего существует как минимум два способа. Первый способ описан Джоном Скитом (Jon Skeet) в его книге C# in depth:
Коротко и лаконично. Мы инициализируем делегат события пустым методом, поэтому он никогда не будет null. Вычесть из делегата этот метод невозможно, т.к. он определен при инициализации делегата и у него нет ни имени, ни ссылки на него из любого места программы.
Второй способ заключается в написании метода, содержащего в себе необходимую проверку на null. Этот прием особенно хорошо работает в .NET 3.5 и выше, где доступны методы расширений (extension methods). Так как при вызове метода расширений объект, на котором он вызывается, является всего лишь параметром этого метода, то этот объект может быть пустой ссылкой, что и используется в данном случае.
Таким образом, мы можем вызывать события как Changed.SafeRaise(this, EventArgs.Empty), что экономит нам строчки кода. Также можно определить третий вариант метода расширений для случая, когда у нас EventArgs.Empty, чтобы не передавать их явно. Тогда код сократится до Changed.SafeRaise(this), но я не буду рекомендовать такой подход, т.к. для других членов вашей команды это может быть не так явно, как передача пустого аргумента.
Тонкость №4 — что не так со стандартной реализацией?
Если у вас стоит ReSharper, то вы могли наблюдать следующее его сообщение. Команда решарпера правильно считает, что не все ваши пользователи достаточно осведомлены в работе событий\делегатов в плане отписки\вычитания, но тем не менее ваши события должны работать предсказуемо не для ваших пользователей, а с точки зрения событий в .NET, а т.к. там такая особенность есть, то и в вашем коде она должна остаться.
Бонус: попытка Microsoft сделать контравариантные события
В первой бете C# 4 Microsoft попытались добавить контравариантности событиям. Это позволяло подписываться на событие EventHandler<MyEventArgs> методами, имеющими сигнатуру EventHandler<EventArgs> и все работало до тех пор, пока в делегат события не добавлялось несколько методов с разной (но подходящей) сигнатурой. Такой код компилировался, но падал с ошибкой времени выполнения. По всей видимости, обойти это так и не смогли и в релизе C# 4 контравариантность для EventHandler была отключена.
Это не так заметно, если опускать явное создание делегата при подписке, например следующий код отлично скомпилируется:
Графический интерфейс и событийные процедуры
Графический интерфейс. Графический интерфейс необходим для реализации интерактивного диалога пользователя с работающим приложением. Основой для создания графического интерфейса разрабатываемого приложения являются форма (в Visual Basic — класс объектов Form, в VBA — класс объектов UserForm), представляющая собой окно, в котором размещаются управляющие элементы. Необходимо отметить, что графический интерфейс проекта может включать в себя несколько форм.
| Форма — это объект, представляющий собой окно
I хуЩ на экране, в котором размещаются управляющие элементы.
Визуальное конструирование графического интерфейса приложения состоит в том, что на форму с помощью мыши помещаются и «рисуются» те или иные управляющие элементы.
Классы управляющих элементов (Controls) имеют различное назначение в графическом интерфейсе приложения. Текстовые поля (TextBox), метки (Label) и списки (ListBox) обычно используются для ввода и вывода данных, графические окна (PictureBox) — для вывода графики, командные кнопки (CommandButton), переключатели (CheckBox) и флажки (OptionsButton) — для организации диалога и так далее.
![]() |
На форму может быть помещено несколько экземпляров одного класса управляющих элементов, например, несколько кнопок Commandl, Command2, Command3 и так далее, каждая из которых обладает индивидуальными значениями свойств (надпись, размеры и др.).
Управляющие элементы — это объекты, являющиеся элементами графического интерфейса приложения и реагирующие на события, производимые пользователем или программными объектами.
Форма и управляющие элементы, обладают определенными наборами свойств, методов и событий (табл. 4.2).
Соглашение об именах объектов. Целесообразно объектам проекта присваивать имена, которые дают возможность распознать их тип и назначение. Принято, что имя начинается с префикса, который определяет тип объекта. Для форм принят префикс f rm, для командных кнопок — cmd, текстовых полей — txt, для надписей — 1Ы и так далее. После префикса идет информативная часть имени, которая пишется с прописной буквы (например, frmFirst, lblText, cmdExit) или содержит число (например, txtl, txt2, txt3).
Событийные процедуры. Для каждого события можно запрограммировать отклик, то есть реакцию объекта на произошедшее событие. Если пользователь производит какое-либо воздействие на элемент графического интерфейса (например, щелчок), в качестве отклика выполняется некоторая последовательность действий (событийная процедура).
Имя процедуры включает в себя имя объекта и имя события.
Событийная процедура представляет собой подпрограмму, которая начинает выполняться после реализации определенного события.
В событийной процедуре может участвовать несколько объектов. Например, само событие происходит с первым объектом (0бъект1), в результате второй (0бъект2) изменяет значение своего свойства, а третий (ОбъектЗ) реализует какой-либо метод и так далее.
Каждая процедура представляет собой отдельный программный модуль, который реализует определенный алгоритм. В терминологии процедурного программирования такие процедуры соответствуют подпрограммам, поэтому каждая из событийных процедур начинается с ключевого слова Sub (subroutine — подпрограмма) и заканчивается ключевыми словами End Sub:
Объект (2) .Свойство = ЗначениеСвойства
Объект(3).Метод арг1:=знач, арг2:=знач
Вопросы для размышления
1. Какие объекты целесообразно использовать при конструировании графического интерфейса проекта, если необходимо: вводить и выводить данные? Выводить надписи?
2. Могут ли в событийной процедуре объекта изменяться его свойства и применяться его методы?
Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого.
Папиллярные узоры пальцев рук — маркер спортивных способностей: дерматоглифические признаки формируются на 3-5 месяце беременности, не изменяются в течение жизни.
Поперечные профили набережных и береговой полосы: На городских территориях берегоукрепление проектируют с учетом технических и экономических требований, но особое значение придают эстетическим.