Employe h реализация обработки событий на c


События и делегаты

В Заметке о консольных и оконных (Windows Forms) приложениях мы отметили существенную разницу между ними. Консольное приложение реализует концепцию императивного (процедурного) программирования, а управление Windows-приложением основано на понятии события (event). События могут создаваться как пользователем, так и возникать в процессе выполнения приложения. Начнем с простых вещей.

Создадим оконное приложение и запустим его на выполнение. С пустой формой мы можем выполнить только несколько стандартных действий: изменить размер, свернуть, свернуть в окно/развернуть и закрыть приложение.

Для изменения размеров формы подведем указатель мыши (1 событие) к любой ее границе (указатель изменит свою форму), перетянем ее в нужном направлении (2 событие) и отпустим кнопку (3 событие). Последние три действия с формой выполняются после клика мышью (событие 4) на кнопках формы.

Ясно, что такое приложение нам не слишком интересно, тем не менее, очевидна связь между событиями и действиями.

Перенесем с Панели элементов на форму объект «Кнопка» (по умолчанию – button1 класса Button). На вкладке «Конструктор» кнопка видна, на вкладке «Код» о ней нет никакой информации. Однако раскрыв файл Form1.Designer.cs, мы увидим в описании класса Form1 поле:

private System.Windows.Forms.Button button1;

которое задает этот объект, а в методе private void InitializeComponent() обнаружим описание его свойств (имя, местоположение, размер, надпись и т.п.).

Запустим программу снова на выполнение. Нажатие на кнопку (событие) не влечет за собой никаких действий (кроме того, что между нажатием и отпусканием кнопки она подсвечивается).

Смотрим книгу «для чайников». В ней написано: чтобы связать это событие с каким-либо действием необходимо всего лишь выполнить двойной клик на кнопке, в окне кода появится заготовка для метода – обработчика события Click:

private void button1_Click(object sender, EventArgs e)
<

>

Увеличим ширину кнопки примерно в три раза. Вставим в тело метода между фигурными скобками оператор:
button1.Text = DateTime.Now.ToString();
Теперь при нажатии кнопки непосредственно на ней мы можем прочитать текущие дату и время нажатия на кнопку.

«Выдающийся» результат! Есть событие, есть реакция на него (обработка события). Как Вам такая автоматизация программирования!

Заметим, что в панели Свойства для объекта button1 на закладке События (щелчок на «желтой молнии») напротив события Click появился метод button1_Click. В окне кода добавили всего один метод с одним оператором в его теле. Что же еще изменилось? Посмотрим содержимое файла Form1.Designer.cs. В нем добавилась загадочная строка:

this.button1.Click += new System.EventHandler(this.button1_Click);

Расшифруем ее. Ключевое слово this – это ссылка на текущий объект Form1 (забавно, что имя объекта совпадает с именем класса). Объект button1 размещен на форме Form1. А Click – очевидно это событие, клик на кнопке. EventHandlerделегат (delegate), представляет метод, который будет обрабатывать событие, не имеющее данных (объявлен в библиотеке System). Тип события обязательно должен совпадать с типом делегата. В скобках указывается имя этого метода button1_Click.
Переведем смысл оператора на русский язык:
Объект.Событие += new Делегат(Метод_обработки);
Символ + определяет подписку обработчика события.
Очевидный вывод: Подписка на событие с использованием делегата приводит к вызову метода при возникновении события.

Возможен ли разрыв связи между событием и методом его обработки? И нет ли у вас ощущения статичности таких связей? Можно ли то же самое достичь программным путем?

Реализуем второй вариант действий:
1) поместим кнопку button1 на форме Form1;
2) в конструктор формы добавим один оператор, тогда:
public Form1()
<
InitializeComponent();
button1.Click += new System.EventHandler(button1_Click);
>
3) в описание класса добавим метод:
private void button1_Click(object sender, EventArgs e)
<
button1.Text = DateTime.Now.ToString();
>
4) запустим программу на выполнение, сравним результаты;
5) появился ли оператор подключения в файле FormDesigner.cs ?

Заметим, что этот файл Form1.Designer.cs является текстовым описанием формы и размещенных на ней элементов после запуска программы, что позволяет отобразить ее в режиме конструктора.

Далее многое можно изменять программным путем.

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

Объект, который вызывает событие, называется отправителем (sender) сообщения, а объект, который сообщение получает – получателем. Роль «почтальона» выполняет делегат. Получатель сообщения имеет метод, который автоматически выполняется в ответ на исходное событие. В нашем примере отправителем и получателем сообщения является объект button1 («makes himself»).

Платформа .NET Framework поддерживает простое программирование событий, из-за чего начинающие программисты часто не вникают в работу событий.

Событием в языке C# называется сущность, предоставляющая две возможности: сообщать об изменениях, а для его пользователей — реагировать на них. В объявлениях классов визуальных компонентов мы найдем большое количество событий, которые могут быть вам полезны. Подсчитайте, сколько событий связано с формой? У меня получилось – 76. А для кнопки – 58, не мало? Если же вам необходимо создать собственное событие, то вы можете его просто объявить:

public event EventHandler myEvent;

Рассмотрим, из чего состоит объявление. Сначала идут модификаторы события, затем ключевое слово event, после него — тип события, который обязательно должен быть типом-делегатом, и идентификатор события, то есть его имя myEvent. Ключевое слово event сообщает компилятору о том, что это не публичное поле, а специальным образом раскрывающаяся конструкция, скрывающая от программиста детали реализации механизма событий (пока это замечание пропустите).

В C# разрешается формировать какие угодно разновидности событий. Но ради совместимости программных компонентов со средой .NET Framework следует придерживаться рекомендаций, которые по существу, сводятся к следующему требованию: у обработчиков событий должны быть два параметра. Первый из них — ссылка на объект, формирующий событие, второй — параметр типа EventArgs, содержащий любую дополнительную информацию о событии, которая требуется обработчику. То есть:

void обработчик(object отправитель, EventArgs е)

Как правило, отправитель — это параметр, передаваемый вызывающим кодом с помощью ключевого слова this. Параметр е типа EventArgs содержит дополнительную информацию о событии и может быть проигнорирован, если он не нужен.
Отметим, что и в первом примере с кнопкой автоматически сгенерировался заголовок метода, обеспечивающего обработку клика мышкой:
private void button1_Click(object sender, EventArgs e)
Сам класс EventArgs не содержит полей, которые могут быть использованы для передачи дополнительных данных обработчику, он служит в качестве базового класса, от которого получается производный класс, содержащий все необходимые поля. Тем не менее, в классе EventArgs имеется одно поле Empty типа static, которое представляет собой объект типа EventArgs без данных.
В среде .NET Framework предоставляется встроенный обобщенный делегат под названием EventHandler . В данном случае тип TEventArgs обозначает тип аргумента, передаваемого параметру EventArgs события.
Для обработки многих событий параметр типа EventArgs оказывается ненужным. Поэтому с целью упрощения создания кода в подобных ситуациях в среду .NET Framework и был внедрен необобщенный делегат типа EventHandler, используемый для объявления обработчиков событий, которым не требуется дополнительная информация о событиях (см. наш первый пример).

Пример использования обобщенного делегата EventHandler

Обобщенный делегат EventHandler используется для
объявления события Ez:

public event EventHandler Ez;

Аргументы, передаваемые в метод, задаются в классе MyEA, который наследуется от класса EventArgs.

Постановка задачи «Управление размерами и цветом формы»

Набор цветов: Red, Green, Blue, Yellow + исходный (добавляйте любые!)
Размеры: 500х150, 550×200, 600×250, 650×300

Элементы управления:
Кнопка button1 — Разрешение/Запрет изменение свойств формы
Кнопка button2 — Перекраска формы в желтый цвет без ограничений
Элемент comboBox1 — для выбора цвета: Red, Green, Blue, прежний
Метка label1 — подсказка: «Выберите цвет закраски формы» к comboBox1.

Начальная форма может выглядеть так:

Создаются два класса:
1) Класс Моих Событий Аргументы:

2) Мой класс Обработка события:

Далее приводится текст файла Form1.cs с комментариями:

Вид формы после перекраски:

Примеры

Другой пример применения делегатов рассмотрен в посте «Делегаты и методы».

Базы данных. Как я делал проект на C#

Нет, безусловно, как и у любого «высокообразованного технаря», в университете у меня были курсы по программированию, где обучали в том числе и вышеобозначенному языку. Кстати, данный факт в резюме и привёл моего начальника со своей просьбой. Что ж, делать нечего, отбросив страх и лень, пришлось взяться за дело. Если вы никогда не кодили на C#, приглашаем вас пройти наш бесплатный интенсив по C# для новичков.

Берёмся за дело

Прежде всего, необходим бесплатный дистрибутив. Приложение нам необходимо для личного пользования, поэтому можно смело идти на официальный сайт Visual Studio Community и запускать процесс скачивания. Почему именно VS, а не более простое решение? Если вкратце, то это большие возможности по развитию, графическому и программному. Пока идёт долгий процесс скачивания и установки, освежаем знания. Основы C# или VS здесь описываться не будут. Просто допустим, что любой человек хоть немного изучавший программирование, прекрасно знает циклы, функции и операторы. Со структурой среды программирования можно разобраться просто скачав один из примеров на сайте разработчика и внимательно его изучив.

Мой же краткий план таков – на первом этапе пишем приложение, которое создаёт базу данных сотрудников и записывает её в формат XML. На втором этапе позаботимся о начальниках, подумав об извлечении информации с использованием различных фильтров. Чтобы немного усложнить конечную задачу, я решил поэкспериментировать с различными подходами – работа с БД напрямую и через коллекции.

В остальном, задача предельно ясна, поэтому можно приступать. Создадим пустой XML файл с требуемой структурой с использованием обыкновенного блокнота:

Заходим непосредственно в Visual Studio и приступаем к самому простому и приятному – созданию нашей формы:

Далее прямо в инициализации формы создадим объекты директории, файла и данных:

Далее привяжем наш XML файл к проекту и загрузим из него данные:

Осталось мелочь – по нажатию кнопки ОК организовать проверку заполнения полей и сохранение данных в созданный файл:

Найдём максимальный ID в нашей таблице, чтобы создать следующий элемент:

Создаем новую строку для нашей таблицы:

И добавляем в неё полученные данные:

Для организации таблицы, извлечём системное время:

Вот, кажется, и всё. Сохраняем данные и перезагружаем файл:

Переходим ко второму этапу. Создаём форму с фильтрами, где поочерёдно структура таблицы будет представлена в формате ListBox-ов. Форма выглядит следующим образом:

Переходим к созданию нашего списка внутри программы. Создаём класс и инициализируем его:

Загружаем данные внутрь программных данных:

Теперь переходим непосредственно к фильтрам. Первый столбец (номинально второй) загружаем полностью.

После чего переходим к фильтрации при обработке изменения выбранного в ListBox:

Послесловие

И далее по аналогии до бесконечности, пока последний запрос не выведет необходимые руководителю поля.
После некоторых доработок и отладок результат был достигнут, а торжественное «Спасибо» — произнесено. Злой рок судьбы теперь каждое утро сталкивает меня и моих коллег с первой частью результатов моего труда и постепенно наводит на размышление, которое я адресую дорогому читателю — а можно ли было сделать лучше и, что самое главное, проще?

Кстати, освоить разработку под Windows можно, обучаясь на профессии «Программист Windows».

Предыстория

Буквально пару недель назад мой начальник обратился ко мне с просьбой — создать простое приложение на C# для регистрации задач и проектов сотрудников нашего отдела. Ничего сложного, стандартная форма с текстовыми полями и кнопками, по заполнению которых в некоторый файл на сервере добавляется строка с введёнными данными. Дополнительное приложение позволяет контролирующему органу извлекать из файла эту информацию, фильтровать и использовать в дальнейшем планировании. Казалось бы, что может быть проще. В этой истории лишь одна загвоздка. Я никогда не работал на C#.

Нет, безусловно, как и у любого «высокообразованного технаря», в университете у меня были курсы по программированию, где обучали в том числе и вышеобозначенному языку. Кстати, данный факт в резюме и привёл моего начальника со своей просьбой. Что ж, делать нечего, отбросив страх и лень, пришлось взяться за дело. Если вы никогда не кодили на C#, приглашаем вас пройти наш бесплатный интенсив по C# для новичков.

Берёмся за дело

Прежде всего, необходим бесплатный дистрибутив. Приложение нам необходимо для личного пользования, поэтому можно смело идти на официальный сайт Visual Studio Community и запускать процесс скачивания. Почему именно VS, а не более простое решение? Если вкратце, то это большие возможности по развитию, графическому и программному. Пока идёт долгий процесс скачивания и установки, освежаем знания. Основы C# или VS здесь описываться не будут. Просто допустим, что любой человек хоть немного изучавший программирование, прекрасно знает циклы, функции и операторы. Со структурой среды программирования можно разобраться просто скачав один из примеров на сайте разработчика и внимательно его изучив.

Мой же краткий план таков – на первом этапе пишем приложение, которое создаёт базу данных сотрудников и записывает её в формат XML. На втором этапе позаботимся о начальниках, подумав об извлечении информации с использованием различных фильтров. Чтобы немного усложнить конечную задачу, я решил поэкспериментировать с различными подходами – работа с БД напрямую и через коллекции.

В остальном, задача предельно ясна, поэтому можно приступать. Создадим пустой XML файл с требуемой структурой с использованием обыкновенного блокнота:

Заходим непосредственно в Visual Studio и приступаем к самому простому и приятному – созданию нашей формы:

Далее прямо в инициализации формы создадим объекты директории, файла и данных:

Далее привяжем наш XML файл к проекту и загрузим из него данные:

Осталось мелочь – по нажатию кнопки ОК организовать проверку заполнения полей и сохранение данных в созданный файл:

Найдём максимальный ID в нашей таблице, чтобы создать следующий элемент:

Создаем новую строку для нашей таблицы:

И добавляем в неё полученные данные:

Для организации таблицы, извлечём системное время:

Вот, кажется, и всё. Сохраняем данные и перезагружаем файл:

Переходим ко второму этапу. Создаём форму с фильтрами, где поочерёдно структура таблицы будет представлена в формате ListBox-ов. Форма выглядит следующим образом:

Переходим к созданию нашего списка внутри программы. Создаём класс и инициализируем его:

Загружаем данные внутрь программных данных:

Теперь переходим непосредственно к фильтрам. Первый столбец (номинально второй) загружаем полностью.

После чего переходим к фильтрации при обработке изменения выбранного в ListBox:

Послесловие

И далее по аналогии до бесконечности, пока последний запрос не выведет необходимые руководителю поля.
После некоторых доработок и отладок результат был достигнут, а торжественное «Спасибо» — произнесено. Злой рок судьбы теперь каждое утро сталкивает меня и моих коллег с первой частью результатов моего труда и постепенно наводит на размышление, которое я адресую дорогому читателю — а можно ли было сделать лучше и, что самое главное, проще?

Кстати, освоить разработку под Windows можно, обучаясь на профессии «Программист Windows».

Разработка приложений на базе WPF

Разработка бизнес-логики

Для реализации функциональности приложения в части обработки информации о сотрудниках предприятия необходимо для страницы PageEmployee установить механизм запуска задач ( Отменить, Создать, Редактировать, Сохранить, Найти и Удалить ) при выборе соответствующих пунктов меню и нажатии на кнопки панели инструментов. Технология WPF предлагает модель команд для выполнения такой привязки.

Модель команд обеспечивает делегирование событий определенным командам и управление доступностью элементов управления в зависимости от состояния соответствующей команды. В WPF команда представляет собой задачу приложения и механизм слежения за тем, когда она может быть выполнена. В то же время сама команда не содержит конкретного кода выполнения задачи. Одна и та же команда может быть привязана к одному или нескольким интерфейсным элементам приложения. Инициируют команду источники, которые могут быть различными элементами управления, например пункты меню MenuItem или кнопки – Button. Целевым объектом команды является элемент, для которого предназначена эта команда .

Классы, реализующие команды должны поддерживать интерфейс ICommand. В этом интерфейсе определены два метода Execute, CanExecute и событие CanExecuteChanged.

В WPF имеется библиотека базовых команд. Команды доступны через статические свойства следующих статических классов:

  • ApplicationCommands ;
  • NavigationCommands ;
  • EditingCommands ;
  • MediaCommands.

Для создания пользовательских команд целесообразно использовать классы RoutedCommand, который имеет реализацию интерфейса ICommand.

В разрабатываемом приложении для функций Отменить, Создать, Сохранить и Найти будем использовать команды и библиотеки WPF – статический класс ApplicationCommands, а для функций Редактировать и Удалить спроектируем пользовательские команды.

Для разработки пользовательских команд добавим в проект папку Commands и в ней создадим класс DataCommands.

В классе DataCommands объявлены два свойства Delete и Edit типа RoutedCommand. Класс RoutedCommand определяет команду, реализующую ICommand. В конструкторе данного класса определяется объект inputs типа InputGestureCollection. Класс InputGestureCollection представляет упорядоченную коллекцию объектов InputGesture, которые позволяют с помощью класса KeyGesture задать комбинацию клавиш для вызова команды.

Для использования страницей PageEmployee пользовательских команд в XAML -документе необходимо добавить пространство имен , где расположен класс DataCommands. Данному пространству имен присвоим ссылку command.

Теперь для страницы PageEmployee сформируем коллекцию объектов CommandBinding, которая осуществляет привязку команд для данного элемента и объявляет связь между командой, ее событиями и обработчиками.

Для класса CommandBinding свойство Command определяет ссылку на соответствующую команду, а свойства Executed и CanExecute задают обработчики событий при выполнении команды.

На странице приложения используются следующие команды: Отменить, Создать, Редактировать, Поиск, Сохранить и Удалить. Команды могут быть доступны или недоступны пользователю при работе приложения. Это проверяет метод CanExecute при генерации события CanExecuteChanged, которое вызывается при изменении состояния команды. Доступность команд определяется состоянием, в котором находится приложение . В тоже время выполнение какой-либо команды переводит, как правило, приложение в какое-либо другое состояние. Для проектируемого приложения можно определить следующие состояния:

  • первоначальная загрузка страницы (1);
  • просмотр данных по всем сотрудникам (2);
  • редактирование данных по отдельному сотруднику (3);
  • создание новой записи по сотруднику в базе данных (4).
Илон Маск рекомендует:  Селекторы типа

На рис. 4.19 приведена диаграмма состояний приложения, на которой кружками обозначены состояния, а дуги соответствуют переходам при выполнении определенной команды.

На основе диаграммы состояний построим таблицу доступности команд в различных состояниях (табл. 4.2).

Таблица 4.2. Доступность команд в различных состояниях приложения
Состояние Доступность команд
Сохранить Отменить Создать Поиск Редактировать Удалить
1 false false true true true true
2 false false true true true true
3 true true false false false false
4 true true false false false false

Из табл. 4.2 видно, что в приложении режим доступности команд в состояниях 1 и 2 противоположно режиму доступности команд в состояниях 3 и 4. Фактически для приложения имеются два режима (один объединяет состояния 1 и 2, а второй – состояния 3 и 4), управлять которыми можно с помощью логической переменной .

В код программы класса PageEmployee введем логическое поле isDirty для управления доступностью команд.

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

В дальнейшем в обработчики добавим код для обеспечения требуемой функциональности.

В коде класса остается добавить обработчики (реализация метода CanExecute ), которые управляют доступностью команд. Так как при анализе табл. 4.2 было выявлено, что для приложения различимо только два состояния доступности команд, то и обработчиков тоже достаточно иметь в программе два.

Теперь необходимо модифицировать XAML -документ в части задания свойства Command при описании пунктов меню и панели инструментов для привязки команд.

При описании меню ( Menu ) XAML -документ модифицирован следующим образом.

Соответствующие изменения XAML -документа необходимо провести и для панели инструментов ToolBar.

При выполнении приложения различные состояния доступности пунктов меню приведены на рис. 4.20 и рис. 4.21.

При выборе какого-либо пункта меню , например пункта Создать, система выдает сообщение ( рис. 4.22).

Следующим этапом разработки приложения является создание источника данных, который обеспечит взаимодействие приложения с базой данных.

Ключевые термины

Приложение WPF, пространство имен , атрибут , частичный класс , простое свойство, сложное свойство, фрейм , гиперссылка , меню , панель инструментов , команда , привязка команд.

MainWindow, xmlns, partial, InitializeComponent, NavigationWindow, Frame, FrameworkElement, Page, Hyperlink , NavigateUri, Menu , MenuItem, DataGrid , DataGr >Execute , CanExecute, CanExecuteChanged, CommandBinding.

Краткие итоги

В данной теме были рассмотрены вопросы разработки страничного WPF-приложения, организации перехода по страницам, создания системы меню , панели команд, табличного представления информации для просмотра и редактирования данных, системы универсальных команд многократного использования.

Employe h реализация обработки событий на c

We recommend upgrading to the latest Google Chrome or Firefox.

Join GitHub today

GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.

ru.ncedu.java.tasks / Employee.java

package ru.ncedu.java.tasks ;
/**
* ЦЕЛЬ ЗАДАЧИ — разобраться с основами объектно-ориентированного программирования в Java,
* принципами написания класса, реализации методов get/set, обращения к полям и методам объектов.
*
* ЗАДАНИЕ
* Реализовать класс, представляющий собой описание сотрудника компании (Employee).
*
* ТРЕБОВАНИЯ
* 1) Экземпляр класса, реализующего данный интерфейс, должен соответствовать одному сотруднику.
* 2) Необходимо реализовать методы get/set для имени (и фамилии), полного имени, зарплаты сотрудника,
* а также его непосредственного менеджера и топового (самого вышестоящего) менеджера.
* Данные (поля) должны быть объявлены как private-переменные класса.
* Методы get** / set** должны оперировать с этими полями.
* 3) По умолчанию (у только что созданного сотрудника) зарплата должна быть равна 1000.
*
* ОБЩИЕ ТРЕБОВАНИЯ (ко всем решениям задач для их автоматической проверки)
* Вот как должна выглядеть реализация данного интерфейса:
* public class EmployeeImpl implements Employee
* Если в коде есть конструкторы, то среди них должен быть конструктор без параметров:
* public EmployeeImpl()
*
* ПРИМЕЧАНИЕ
* Задачу можно решать без явной обработки и генерации исключительных ситуаций (Exceptions).
*
* @author Alexander Kharichkin
* @author Yuriy Popov
*/
public interface Employee <
/**
* @return Зарплата сотрудника на настоящий момент.
*/
int getSalary ();
/**
* Увеличивает зарплату сотрудника на заданное значение
* @param value Значение, на которое нужно увеличить
*/
public void increaseSalary ( int value );
/**
* @return Имя сотрудника
*/
public String getFirstName ();
/**
* Устанавливает имя сотрудника
* @param firstName Новое имя
*/
public void setFirstName ( String firstName );
/**
* @return Фамилия сотрудника
*/
public String getLastName ();
/**
* Устанавливает фамилию сотрудника
* @param lastName Новая фамилия
*/
public void setLastName ( String lastName );
/**
* @return Имя и затем фамилия сотрудника, разделенные символом » » (пробел)
*/
public String getFullName ();
/**
* Устанавливает Менеджера сотрудника.
* @param manager Сотрудник, являющийся менеджером данного сотрудника.
* НЕ следует предполагать, что менеджер является экземпляром класса EmployeeImpl.
*/
public void setManager ( Employee manager );
/**
* @return Имя и фамилия Менеджера, разделенные символом » » (пробел).
* Если Менеджер не задан, возвращает строку «No manager».
*/
public String getManagerName ();
/**
* Возвращает Менеджера верхнего уровня, т.е. вершину иерархии сотрудников,
* в которую входит данный сотрудник.
* Если над данным сотрудником нет ни одного менеджера, возвращает данного сотрудника.
* Замечание: поскольку менеджер, установленный методом < @link #setManager(Employee)>,
* может быть экзепляром другого класса, при поиске топ-менеджера нельзя обращаться
* к полям класса EmployeeImpl. Более того, поскольку в интерфейсе Employee не объявлено
* метода getManager(), поиск топ-менеджера невозможно организовать в виде цикла.
* Вместо этого нужно использовать рекурсию (и это «более объектно-ориентированно»).
*/
public Employee getTopManager ();
>
  • © 2020 GitHub , Inc.
  • Terms
  • Privacy
  • Security
  • Status
  • Help

You can’t perform that action at this time.

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.

Как правильно обрабатывать событие Click в WinForms? C#

Есть форма Form1 и одна единственная кнопка button1

Задумался над тем как правильно обрабатывать событие Click , в том плане, что не просто писать кучу кода вместо do something , а сделать это по крайней мере в стиле ООП. Вопрос 1. Подскажите пожалуйста, как решают этот вопрос при разработке коммерческих программ?

Сейчас я делаю примерно так

Я думаю суть понятна. Вопрос 2. Подскажите пожалуйста, правильно ли я делаю, написав код таким образом?

Всем большое спасибо за ответы!

1 ответ 1

В этом видео дается самый простой пример MVP, без привязок и с простейшей переброской событий от кнопок в Presenter . Если вы начнете писать что-то более серьезное, то быстро осознаете необходимость работы с привязками, потому стоит внимательно ознакомиться с классом BindingSource.

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

1) Пример на простейшее событие выбора в ComboBox


Создадим такой класс, который потом будет использовать для создания свойства в форме

Тогда вьюшка будет такой

А презентер такой

2) Событые имеет значемые для презентера параметры.

Создадим такой класс посредник

Параметры в презентер будем передовать в словаре вот так

3) Пример на кнопки с привязкой к свойствам Enabled и Text

Для кнопки слева сделана привязка к свойству Enabled с помощью такого класса

Для кнопки справа используется привязка к свойствам Enabled и Text

Презентер к ней

4) Кнопка на ToolStrip

Имеет такую особенность, что у нее изначально нет поддержки привязок. Поэтому нам понадобится такой класс унаследованный класс кнопки

После добавления в проект такого класса и компиляции эту кнопку можно будет добавить к тулбар и работать с ней таким образом

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

Книги онлайн

. . . все ваши любимые книги онлайн

«Обработка событий в С++»

Александр Клюев Обработка событий в С++

Введение

Так уж исторически сложилось, что в языке С++ нет событий. Событием (event) является исходящий вызов (программисты на VB хорошо знакомы с ними) и в С++ их действительно нет. Иногда события путают с сообщениями (message), но это не верно. Сообщение это прямой вызов: например windows вызывает оконную процедуру для передачи собщения окну. Объект (система) вызывает функцию обькта(окна). Вызов происходит от объекта к объекту. В отличии от сообщения событие имеет другую механику. Объект инициирует событие и вызываются все объекты-обработчики. Т.е. от одного объекта к нескольким. Причем объект инициатор события может ничего не «знать» об его обработчиках, поэтому событие называют исходящим вызовом.

Раз уж в С++ события на уровне языка не поддерживаются, значит стоит организовать их на уровне библиотеки. Здесь приведена реализация такой библиотеки. В ней есть два класса signal и slot.

Итак, чтобы сделать какой нибудь класс источником события поместите в него переменную типа signal:

signal someEvent; // void – тип аргумента события

А чтобы сделать класс обработчиком поместите в него переменную типа slot, функцию обработчик и свяжите slot с обработчиком:

slot someHandler; // переходник

// функция обработчик события

void connect (EventRaiser& er) <

someHandler.init(er.someEvent, onEvent, this); // установим связь события с обработчиком

Так как эти объекты являются частью своих хозяев, не нужно заботится о времени жизни связи. Ее разрыв произойдет во время разрушения одного из них. Событие же инициируется вызвовом метода signal::raise:

signal someEvent; // void – тип аргумента события

someEvent.raise(); // инициация события

Пример

В примере создаются два класса обработчик и инициатор события, устанавливается связь между ними и иллюстрируется обработка события в нескольких объектах одновременно:

signal event; // const char* – тип аргумента. может быть void

void raise(const char *eventName) <

printf(«raising %s event\n», eventName);

> g_Raiser; // глобальный объект

const char *color;

slot handler; // переходник

void onEvent(const char *eventName) < // обработчик события

printf(«\t%s event handled in %s object\n», eventName, color);

EventHandler(const char *clr): color(clr) <

handler.init(g_Raiser.event, onEvent, this); // установим связь

int main(int argc, _TCHAR* argv[]) <

g_Raiser.raise(«Small»); // событие обработается в red

g_Raiser.raise(«Big»); // событие обработается в red и blue

g_Raiser.raise(«Medium»); // событие обработается в red и green.

// объект blue уничтожен, связь разорвана

Краткое описание классов

signal – cобытие (детали реализации опущены)

template // Arg – тип аргумента функции обработчика

Arg arg // Арумент arg будет передан в обработчики события

slot – переходник для обработки события в классе-обработчике (детали реализации опущены)

// установить связь с событием и обработчиком

void (Owner::*mpfn)(Arg), // функция обработчик

Owner *This // обьект обработчик

// установить связь с событием и обработчиком для случая signal

signal &sig, // событие

void (Owner::*mpfn)(), // функция обработчик

Owner *This // обьект обработчик

Исходный код

Весь код находится в файле sigslot.h

// sigslot.h – autor Kluev Alexander [email protected]

template class signal;

friend class signal_base;

typedef void (Thunk::*Func)();

slot(): _trg(0), _mfn(0), _prev(0), _next(0) <>

if (_next) _next->_prev = _prev;

if (_prev) _prev->_next = _next;

_prev = _next = 0;

void init(signal &sig, void (Owner::*mpfn)(), Owner *This) <

_mfn = (Func)mpfn; sig._add(*this);

void _call(Arg a) <

typedef void (Thunk::*XFunc)(Arg);

XFunc f = (XFunc)_mfn;

friend class slot;

if (_head._next) _head._next->_prev =&s;

void _raise(Arg a) <

slot *p = _head._next;

slot *p = _head._next;

while (_head._next) _head._next->clear();

class signal: public signal_base <

typedef void VOID;

void signal ::raise() <

Комментарии:

Не всегда корректный код

Вы приводите указатель на функцию-член класса клиента к указателю на функцию из конкрентного класса (slot::Thunk), это для некоторых классов может быть невозможно, ошибка компилятора, что-то типа «указатели имеют разную природу», наблюдатась для WTL проекта, я в свое время не стал углубляться, удалось обойтись.

Кстати эта проблема нашла отражение в FLTK (библиотека типа WTL/Qt, etc., )/– там все события вызывают статические функции с параметром-указателем this:

static void static_cb(void* v) <

В C++ указатели на функцию-член не всегда просто адрес функции, нельзя приводить указатель на функцию одного класса к указателю на функцию другого. Однако возможно есть один способ:

void call(TyClass* p_this) <(

т.е. сделать обычную функцию с параметром this, параметризованную функцией-членом, а на эту обычную функцию уже хранить указатель.

typedef void (*call_f_type)(void*);

Проблема здесь в том, что VC++ может не понять, что (call ) означает, что надо сгенерировать функцию и взять указатель на нее, ну и конечно как изменить Ваш пакет – как известно удобство важнее всего.

Интересно как это сделано в boost.

yaroslav_v 10.2.2003 17:11

делов-то

На самом деле ничего принципиально нового тут нет. Обычный callback. Чем это принципиально лучше чем ConnectionPoints из COM?

Евгений Коробко 10.2.2003 12:13

в Boost есть реализация подобного интересна тем, что:

• также является шаблонным классом

• слот может реагировать на несколько сигналов

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

• можно передавать не только объект-слот, но и просто указатель на функцию и работать будет с тем же успехом…

так что, конечно неплохо, но та реализация, IMHO, лучше…

null 6.2.2003 13:10

Не хуже, чем в QT ихние эвенты. И не надо макросов гопницких

Huang Bai Wei 5.2.2003 13:40

Оглавление

Копирование материалов сайта www.bookol.ru
допускается только с письменного разрешения
администрации сайта.

Информационная продукция сайта
запрещена для детей (18+).
© 2010 -2020 «Книги онлайн»

Реализация обработчика событий С#

Приложение c# в несколько раз записывает таблицу MS SQL DB. записи этой таблицы должны быть прочитаны и обработаны другим приложением c# .

На данный момент я реализовал таймер Threading, который выглядит (каждые 2 секунды), если таблица имеет строки и обрабатывает данные:

Моя проблема в том, что текущая реализация не эффективна. Проверка таблицы таймером требует ресурсов. Я хотел бы сделать это в случае, когда это происходит. В идеале я хотел бы реализовать и обработчик событий, поднятый add record to table1 событие add record to table1 . Если это возможно (поскольку я никогда не выполнял обработчик событий), я был бы признателен за любые отзывы о том, как это можно сделать.

В SQL Server есть некоторые функции отслеживания изменений, наиболее заметные из которых SqlDependency через SqlDependency но, откровенно говоря, я думаю, вам лучше взглянуть на отдельный механизм уведомления. Например, я большой поклонник redis pub/sub, потому что это смешно просто настроить (черт возьми, выделенный pub-sub-сервер даже не требует настойчивости, поэтому проблема «bgsave»/»fork» делает redis сложно в окнах не применяется, поэтому вы можете просто использовать redis-сервер, доступный в nuget). Затем вы просто подписчитесь на именованный канал, и у вас есть другие части системы, которые передают сообщение этому именованному каналу, когда они добавляют работу. Простой и эффективный. Для обеспечения надежности вы также хотите периодически опроса вручную, но, вероятно, на гораздо более медленном опросе — возможно, каждые 30 секунд или что-то в этом роде.

Здесь пример pub/sub, использующий redis через BookSleeve (вам также понадобится redis-сервер, запущенный на локальном компьютере):

Событийно-управляемое программирование

В основу Windows положен принцип событийного управления. Это значит, что и сама система, и приложения после запуска ожидают действий пользователя и реагируют наних заранее заданным образом. Любое действие пользователя (нажатие клавиши на клавиатуре, щелчок кнопкой мыши, перемещение мыши) называется событием. Структура программы, управляемой событиями, изобра­жена нарис. 14.1.

Рис. 14.1. Структура программы, управляемой событиями

Событие воспринимается Windows и преобразуется в сообщение — запись содержащую необходимую информацию о событии (например, какая клавиш была нажата, и каком месте экрана произошел щелчок мышью). Сообщения могут поступать не только от пользователя, но и от самой системы, а также о активного или других приложений. Определен достаточно широкий круг стандартных сообщений, образующий иерархию, кроме того, можно определять собственные сообщения.

Сообщения поступают в общую очередь, откуда распределяются по очередям приложений. Каждое приложение содержит цикл обработки сообщений, который выбирает сообщение из очереди и через операционную систему вызывает подпрограмму, предназначенную для его обработки (рис. 14.2). Таким образом Windows-приложение состоит из главной программы, обеспечивающей инициализанию и завершение приложения, цикла обработки сообщений и набора обработчиков событий.

Рис. 14.2. Структура Windows-приложения

Среда Visual Studio.NET содержит удобные средства разработки Windows-npи ложений, выполняющие вместо программиста рутинную работу — создание шаб лонов приложения и форм, заготовок обработчиков событий, организацию цик ла обработки сообщений и т. д. Рассмотрим эти средства.

Шаблон Windows-приложения

Создадим новый проект (File ► New ► Project), выбрав шаблон Windows Application (рис. 14.3). После более длительных раздумий, чем для консольного приложе­ния, среда сформирует шаблон Windows-приложения. Первое отличие, которое бросается в глаза, — вкладка заготовки формы Form1.cs[Design], расположенная в основной части экрана. Форма представляет собой окно и предназначена для размещения компонентов (элементов управления) — меню, текста, кнопок, спи­сков, изображений и т. д.

Рис. 14.3. Выбор шаблона проекта

Среда создает не только заготовку формы, но и шаблон текста приложения. Пе­рейти к нему можно, щелкнув в окне Solution Explorer (View ► Solution Explorer) правой кнопкой мыши на файле Form1.cs и выбрав в контекстном меню команду View Code. При этом откроется вкладка с кодом формы, который, за исключе­нием комментариев, приведен в листинге 14.1. Представлять себе, что написано в вашей программе, весьма полезно, поэтому давайте внимательно рассмотрим этот текст.

Листинг 14.1.Шаблон Windows-приложения

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

Предыдущие версии 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

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

  • Объектная переменная 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._

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

Простейшее инициирование событий

Давайте вернемся к простому классу 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

Вместо выделенной команды, выводящей текстовое сообщение на консоль, должно инициироваться событие. Задача решается в несколько этапов. В простейшем случае в классе сначала объявляется открытая переменная с ключевым словом 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. Обратите особое внимание на некоторые особенности этого объявления:

  • Класс источника должен быть указан явно, объявления вида As Object недопустимы.
  • Объявление располагается на уровне модуля или класса и не содержит ключевого слова New.

После включения этой строки в программу объектная переменная anEmpl oyee может использоваться всюду, где вас интересует событие SalarySecurityEvent. Как показано на рис. 6.2, IDE автоматически создает обработчик события с именем, построенным по схеме А_В, для каждой объектной переменной, объявленной с ключевым словом Wi thEvents. Чтобы вызвать автоматически сгенерированный «скелет» события, достаточно выбрать его в раскрывающемся списке, как на рис. 6.2.

А теперь давайте объединим все сказанное на практическом примере. Создайте консольное приложение и включите следующий фрагмент в первый (стартовый) модуль:

Private WithEvents anEmployee As EmployeeWithEvents

Dim tom As New EmployeeWithEvents(«Tom». 100000)

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

Обратите внимание на символ подчеркивания, добавленный 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)

MsgBox(Sender.TheName &»had an improper salary raise attempted!»)

От приемника событий мы переходим к источнику. В класс 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)

End Sub Readonly Property TheName() As String

End Property Readonly Property Salary() As Decimal s,

Public Overloads Sub RaiseSalary(ByVal Percent As Decimal)

If Percent > LIMIT’Then

‘ Операция запрещена — необходим пароль

RaiseEvent SalarySecurityEventtMe, New System.EventArgs())

m_Sa1ary = (1 + Percent) * m_Salary

Public Overloads Sub RaiseSalary(ByVal Percent As Decimal.

ByVal Password As String)

If Password = «special» Then

m_Salary = (1 + Percent) * m_Salary

Первый выделенный фрагмент объявляет событие, а второй инициирует его при попытке недопустимого повышения зарплаты.

Примерный результат запуска программы показан на рис. 6.4. При нажатии кнопки ОК окно сообщения исчезает, и в консольном окне выводится строка, из которой видно, что зарплата Тома не изменилась.

Переменные WithEvents потребляют системные ресурсы. Как только такая перемен-ная становится ненужной, присвойте ей Nothing.

Рис. 6.4. Окно сообщения, вызываемое при обработке события

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

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

Public Class ImproperSalaryRaiseEvent

Private m_Message As String

Private m_theRaise As Decimal

Sub New(ByVal theRaise As Decimal. ByVal theReason As String)

Readonly Property Message() As String

End Get End Property Readonly Property theRaise() As Decimal

После того как этот класс будет включен в решение, следует внести небольшие изменения в объявление события в классе Empl oyee:

Public Event SalarySecurityEvent(ByVal Sender As

CustomEventArgExample.EmployeeWithEvents. ByVale As

Теперь во втором аргументе передается переменная класса ImproperSalaryRai seEvent. Следующие изменения вносятся во фрагмент, в котором непосредственно вызывается событие:

Public Overloads Sub RaiseSalary(ByVal Percent As Decimal)

If Percent > LIMIT Then

‘ Операция запрещена — необходим пароль

New ImproperSalaryRaiseEvent(Percent, «INCORRECT PASSWORD!»))

m_Salary =(1 + Percent) * m_Salary

Остается лишь слегка исправить код обработчика события (изменения выделены жирным шрифтом).

Private WithEvents anEmployee As EmployeeWithEventsII Sub Maine)

Dim tom As New EmployeeWithEventsII(«Tom». 100000)

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»)

Public Sub anEmployee_SalarySecuhtyEvent(ByVal Sender _ As

CustomEventArgExample.EmployeeWithEvents. ByVal e As

MsgBox(Sender.TheName & «had an improper salary raise of » & _ FormatPercent(e.theRaise) & «with INCORRECT PASSWORD!»)

Результат показан на следующем рисунке. Как видно из рисунка, данные о запрошенном росте заработной платы доступны в обработчике события.

Динамическая обработка событий

Основной проблемой синтаксиса WithEvents является его недостаточная гибкость. Обработчики событий нельзя динамически устанавливать и отключать на программном уровне — фактически вся схема обработки событий жестко фиксируется в программе. Однако в VB .NET поддерживается другой способ динамической обработки событий, значительно более гибкий. Он основан на возможности указания процедуры класса-приемника, вызываемой при возникновении события (исключение добавленных обработчиков также происходит динамически).

Конечно, для установки обработчика события необходимо зарегистрировать не только класс-приемник, но и метод, который должен вызываться при возникновении события. Для этой цели применяется команда AddHandler, которой при вызове передаются два параметра:

  • имя события в классе-источнике;
  • адрес метода (процедуры событий) класса-приемника, вызываемого при возникновении события.

Код AddHandl ег включается в класс-приемник, а не в класс-источник. Адрес метода, вызываемого при возникновении события, определяется оператором AddressOf. При вызове AddressOf передается имя метода объекта класса-приемника. Например, следующая команда устанавливает динамический обработчик события для объекта

AddHandler tom.SalarySecurityEvent.AddressOf anEmp1oyee_SalarySecurityEvent

В результате тестовая программа будет обнаруживать событие Sal arySecuri tyEvent объекта tom и в случае его возникновения — вызывать процедуру anEmployee_SalarySecurityEvent текущего модуля (разумеется, процедура anEmployee_SalarySecurityEvent должна обладать правильной сигнатурой!).

Ниже приведен фрагмент решения AddHandlerExamplel (ключевые строки выделены жирным шрифтом):

Private WithEvents anEmployee As EmployeeWithEvents Sub Main()

Dim torn As New EmployeeWithEvents(«Tom». 100000)

Console.WriteLine(tom.TheName & «has salary » & tom.Salary)

tom.RaiseSalary(0.2D) ‘ Суффикс D — признак типа Decimal

Console.WriteLine(tom.TheName & «still has salary » & tom.Salary)

Console.WriteLine(«Please press the Enter key»)

Public Sub anEmployee_SalarySecurity£vent(ByVal Sender _

As AddHandlerExamplel.EmployeeWi thEvents,_

ByVal e As AddHandlerExamplel.ImproperSalaryRaiseEvent)_

MsgBox(Sender.TheName & «had an improper salary raise of » & _

FormatPercent(e.theRaise) & «with INCORRECT PASSWORD!»)

Команда AddHandler обладает просто невероятной гибкостью. Например, установка обработчиков событий может зависеть от имени типа:

If TypeName(tom)=»Manager» Then

AddHandler tom.SalarySecurityEvent.AddressOf _

Кроме того, один обработчик событий можно связать с несколькими разными событиями, происходящими в разных классах. Это позволяет выполнять в VB .NET централизованную обработку событий с динамическим назначением обработчиков — в VB такая возможность встречается впервые. В приведенном ниже листинге инициируются разные события в зависимости от переданных параметров командной строки. Главное место в нем занимают фрагменты вида

При передаче в командной строке аргумента 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

m_EventGenerator= New EventGenerator()

Dim commandLinesOAs String = System.Environment.GetCommandLineArgs

If commandLines.Length = 1 Then

MsgBox(«No command argument.program ending!»)

Dim theCommand As String = commandLines(l)

Console.WriteLine(«Thecommand lineoption is» StheCommand)

‘ Проверить параметр командной строки и назначить

‘ соответствующий обработчик события.

Select Case theCommand Case «first»

AddHandler m_EventGenerator.TestEvent. AddressOf

AddHandler m_EventGenerator.TestEvent,_ AddressOf

AddHandler m_EventGenerator.TestEvent. AddressOf

Console.WriteLine(«Press enter to end.»)

‘ Обработчик по умолчанию для непустой командной строки

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 ())

Public Class EventGenerator

‘ В классе определяется только одно событие

Public Event TestEvent(ByVal sender As Object, ByValevt As EventArgs)

‘ Также можно было использовать конструктор по умолчанию

.Public Function GetDescription() As String

Return «EventGenerator class»


‘ Процедура вызывается для инициирования событий

Public Sub TriggerEvents()

Dim e As System.EventArgs = New System.EventArgs()

Отключение обработчиков событий

Обработчики событий, динамически назначаемые командой AddHandler, отключаются командой RemoveHandler, которой должны передаваться точно такие же аргументы, как и при соответствующем вызове AddHandlеr. Обычно для удаления динамически назначаемых обработчиков хорошо подходит метод Dispose. По этой причине в каждом классе, использующем динамическое назначение обработчиков, рекомендуется реализовать интерфейс IDisposable — это напомнит пользователям класса о необходимости вызова Dispose.

Обработка событий в иерархии наследования

Производный класс может в любой момент инициировать открытые или защищенные события своего базового класса, при этом событие идентифицируется ключевым словом MyBase. Кроме того, производные классы автоматически наследуют все обработчики открытых и защищенных событий своих предков. Время от времени в производном классе возникает необходимость в переопределении методов, используемых при обработке открытых и защищенных событий базового класса. Для этой цели используется конструкция Handles MyBase. Пример:

Public Class ParentClass

Public Event ParentEventtByVal aThing As Object.

ByVal E As System.EventArgs)

‘ Программный код End Class

Public Class ChildClass

Sub EventHandler(ByVal x As Integer)

Handles MyBase ParentEvent

‘Обработка событий базового класса

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

Механизм обратного вызова (а следовательно, и события) в VB .NET зависит от особой разновидности объектов .NET, называемых делегатами. Делегат является экземпляром класса System.Delegate. В простейшем случае в делегате инкапсулируется объект и адрес заданной функции или процедуры этого объекта. Такие делегаты идеально подходят для схем обратного вызова вроде той, что используется при обработке событий. Почему? Потому что делегат содержит всю информацию, необходимую для обратного вызова, и может использоваться для вызова нужного метода объекта-приемника.

Но прежде, чем переходить к описанию работы с делегатами, стоит подчеркнуть одно важное обстоятельство. Хотя обработка событий на платформе .NET основана на использовании делегатов, в подавляющем большинстве случаев вам не придется работать непосредственно с делегатами. Команда AddHandl ег предоставляет в ваше распоряжение все необходимое для гибкой обработки событий в VB .NET (впрочем, как вы вскоре увидите, у делегатов есть и другие применения).

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

Традиционные средства вызова функции по адресу были основаны на языковой поддержке указателей на функции. Как показано в следующей врезке, применение указателей на функции сопряжено с потенциальным риском. По сравнению с указателями на функции делегаты обеспечивают дополнительную защиту, значение которой не стоит недооценивать.

Указатели на функции в VB6 . При вызовах функций API часто передается адрес функции для обратного вызова, поэтому в VB6 поддерживался оператор AddressOf. В VB6 адрес функции мог передаваться при любом вызове API. Но что происходило, если список параметров функции, адрес которой передавался при вызове, отличался от предполагаемого? Обычно это приводило к общей ошибке защиты (GPF) и даже к фатальным сбоям с появлением синего экрана.

Таким образом, произвольные указатели на функции обладают принципиальным недостатком: компилятор не может проверить, что такой указатель относится к функции правильного типа. Делегаты представляют собой разновидность указателей на функции, безопасных по отношению к типам. Следуя принципу «доверяй, но проверяй», компилятор автоматически проверяет сигнатуру вызываемой функции — такой вариант работает гораздо надежнее.

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

‘ Использовать конструктор по умолчанию

Public Sub TestSub(ByVal aString As String)

Console. WriteLine(aString SaString)

Чтобы создать делегат для обратного вызова этой процедуры, необходимо сообщить компилятору об использовании делегата для процедуры с одним строковым параметром. Первый шаг этого сценария выполняется за пределами Sub Main следующей строкой:

Public Delegate Sub StringSubDelegate(ByVal

aString As String)

Обратите внимание: в этой строке мы не объявляем делегат, а определяем его. Компилятор VB .NET автоматически создает новый класс StringSubDel egate, производный от System . Delegate 1 .

Далее в процедуре Sub Main экземпляр класса делегата создается оператором AddressOf для адреса процедуры, имеющей правильную сигнатуру. VB .NET автоматически вычисляет объект по полному имени процедуры. Команда создания экземпляра выглядит так:

aDel egate = AddressOf test.TestSub

Компилятор VB .NET понимает, что делегат создается для объекта test. Также можно воспользоваться ключевым словом New, однако это делается редко, поскольку New неявно вызывается в первой форме:

aDelegate = New StringSubDelegate(AddressOf test.TestSub)

После того как делегат будет создан, инкапсулированная в нем процедура вызывается методом Invoke класса Delegate, как в следующем фрагменте:

Dim test As New ClassForStri ngSubDelegate()

Dim aDelegate As StringSubDelegate

aDelegate = AddressOf test.TestSub

На самом деле использовать Invoke необязательно — достаточно передать делегату нужные параметры. VB .NET поймет команду aDelegate(» Hello»), которая выглядит значительно проще.

В этом нетрудно убедиться, просматривая полученный IL-код при помощи программы ILDASM.

Согласитесь, такой способ вывода в консольном окне строки «HelloHello» выглядит несколько необычно!

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

Public Delegate Sub StringSubDelegate(ByVal aString As String)

Dim test As New ClassForStringSubDelegate()

Dim aDelegate As StringSubDelegate

aDelegate — AddressOf test.TestMsgBox

‘ Использовать конструктор по умолчанию

Public Sub TestSub(ByVal aString As String)

Public Sub TestMsgBox(ByVal aString As String)

End Class End Module

Поскольку для делегата важна только сигнатура инкапсулированного метода, он легко «переключается» на другой метод. Потребовалось создать новую версию для вывода информации в окне отладки (вместо консоли и окна сообщения)? Достаточно внести несколько изменений в делегат и добавить в класс функцию, инкапсулируемую делегатом.

Важнейшая особенность делегатов заключается в том, что связывание с методом производится на стадии выполнения. Таким образом, делегаты в сочетании с явным или неявным вызовом метода Invoke по своим возможностям значительно превосходят функцию VB6 CallByName.

Практический пример: специализированная сортировка

Предыдущие примеры выглядят искусственно и относятся к категории «игрушечных программ». В этом разделе мы покажем, как использовать делегаты при специализированной сортировке — одной из стандартных областей применения функций обратного вызова. Общая идея заключается в том, что один метод сортировки в зависимости от ситуации может использовать разные критерии сортировки. Предположим, у вас имеется массив имен: «Mike Item», «Dave Mendlen», «Alan Carter», «Tony Goodhew», «Ari Bixhorn», «Susan Warren»-.

Если вызвать метод Sort класса Array, сортировка будет произведена по именам. А если вы хотите отсортировать массив по фамилиям?

Возможный подход к решению этой задачи описан в главе 5 — вы можете написать собственную реализацию интерфейса IComparer и передать ее Sort. Применение обратного вызова на базе делегатов выглядит чуть более элегантно и теоретически обладает большей гибкостью, поскольку программист может определить собственные процедуры сортировки, которые в конкретной ситуации работают эффективнее стандартных алгоритмов.

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

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

  1. Начать с первого элемента.
  2. Последовательно просмотреть все остальные элементы. Если очередной элемент окажется меньше текущего первого элемента, поменять их местами.
  3. Начать со второго элемента, просмотреть все остальные элементы.
  4. Продолжать до последнего элемента. Основной код волновой сортировки выглядит так:

For i =bottom To (top — bottom) For j =i + 1 To top

Чтобы реализовать этот алгоритм с применением функций обратного вызова, необходимо определить класс Special Sort с делегатом, используемым при обратном вызове. Код этого класса приведен ниже:

1 Public Class Special Sort

2 ‘ Определение делегата

3 Public Delegate Function SpecialCompareCallback(ByVal flrstString _

As String,ByVal secondString As String) As Boolean

4 ‘ Определение процедуры, вызываемой делегатом

5 Public Shared Sub IfySort(ByVal Stuff As String()._

ByVal MyCompare As SpecialCompareCallback)

6 Dim i, j As Integer

7 Dim temp As String

8 Dim bottom As Integer = Stuff.GetLowerBound(0)

9 Dim top As Integer = Stuff.GetUpperBound(0)

10 For i = bottom To (top = bottom)

11 For j = i + 1 To top

12 If MyCompare(Stuff(j). Stuff(i)) Then

13 temp = Stuff(i)

14 Stuff(1) — Stuff (j)

15 Stuff(j) = temp

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

В строке 5 определяется общая процедура, одним из параметров которой является переменная с типом делегата. Таким образом, в ключевой строке 12:

If MyCompare(Stuff(j). Stuff(i)) Then

функция сравнения, инкапсулированная в делегате MyCompare, может относиться к другому классу! Например, если определить приведенный ниже класс, эта схема позволит использовать любой из его методов Compare (обратите внимание: методы Compare объявлены общими, поэтому для их вызова нам даже не нужно создавать конкретный экземпляр класса):

Public Class MyCustomCompare

Public Shared Function TheBasicComparetByVal firstString As String,

ByVal secondString As String) As Boolean

Public Shared Function TheSpecialCompare(ByVal firstString As String.

ByVal secondString As String)As Boolean Dint tokensl,tokens2 As String()

‘ Сравнение по фамилии!

Класс содержит две общие функции, которые ниже будут использованы для создания делегатов. Первая функция, TheBasicCompare, просто сравнивает строки в алфавитном порядке. Более интересная функция TheSpecialCompare предполагает, что строка передается в формате «имя фамилия», и сравнивает фамилии, выделяя их при помощи удобной функции Split.

Остается лишь создать экземпляры класса SpecialSort и делегаты. Это происходит в следующей функции Main (ключевые строки выделены жирным шрифтом):

1 Module Modulel

4 «Tony Goodhew»,»An Bixhorn».»Susan Warren»>

5 ‘ Объявить переменную обратного вызова в форме класс.делегат

6 Dim MyCallBack As Special Sort.SpecialCompareCal1back

7 MyCallBack = AddressOf MyCustomCompare.TheBasicCompare

9 Console.WriteLine(«Here is a basic sort by FIRST name»)

10 Dim temp As String

11 For Each temp In test

14 ‘ Передать другую процедуру сравнения

15 MyCallBack = AddressOf MyCustomCompare.TheSpecialCompare

16 Sped al Sort. MySort (test. MyCallBack)

18 Console.WriteLineC’Here is a sort by LAST name»)

19 For Each temp In test

22 Console. ReadLine()

В строке 6 объявляется «псевдоуказатель на функцию». Чтобы задать его значение, мы передаем адрес функции с правильной сигнатурой (строки 7-15). Поскольку функции объявлены общими, создавать экземпляр класса MyCustomCompare для этого не нужно. После создания делегата в строках 8 и 16 вызывается нужная процедура сортировки класса Special Sort. Поскольку при вызове MySort передается делегат, процедура обращается к классу MyCustomCompare и узнает, по какому критерию должно осуществляться сравнение.

В приведенных выше примерах в делегате инкапсулировался адрес одной функции или процедуры. Нередко в делегатах требуется инкапсулировать сразу несколько процедур (инкапсуляция нескольких функций особого смысла не имеет — каким должно быть возвращаемое значение?). Подобные делегаты называются групповыми (multicast) и реализуются в виде делегата, содержащего несколько однотипных делегатов. При наличии группового делегата все инкапсулированные процедуры вызываются одним методом Invoke, причем это происходит в соответствии с порядком занесения их делегатов в групповой делегат.

Чтобы создать групповой делегат, следует объединить минимум двух делегатов одного типа и присвоить результат переменной того же типа. Задача решается статическим методом Combine класса System.Delegate, который возвращает новый делегат.

Допустим, firstDel и secDel — экземпляры класса MyMultiCastDelegate. Следующая команда объединяет firstDel и secDel в групповой делегат, хранящийся в

firstDel: firstDel =System.Delegate.Combine(firstDel,secDel)

Ниже приведено простое приложение, объединяющее адреса нескольких функций в групповом делегате:

1 Option Strict On

2 Module Modulel

4 Console.WriteLine(«Calling delegate function. «)

5 RegisterDelegate(AddressOf CallBackHandlerl)

6 RegisterDelegate(AddressOf CallBackHandler2)

7 Call Delegates ()

9 «Finished calling.delegate function. «)

12 Public Sub CallBackHandlerHByVal lngVal As RETURNJALUES)

13 Console.WriteLine(«Callback 1 returned » & IngVal)

15 Public Sub CallBackHandler2(ByVallngVal As RETURNJALUES)

16 Console.WriteLine(«Callback 2 returned » & IngVal)

19 Module Module2

20 Public Delegate Sub CallBackFunc(ByVallngValAs RETURN_VALUES)

21 Private m_cbFunc As CallBackFunc

22 Public Enum RETURN_VALUES

26 Public Sub RegisterDelegate(ByRef cbFunc As CallBackFunc)

27 m_cbFunc = CType(System.Delegate.Combine(_

30 Public Sub Call Delegates ()

31 Dim IngCounter As Long = 0

32 ‘ Вызвать процедуры через делегата

33 ‘ и вернуть признак успешного вызова

34 m_cbFunc(RETURN VALUES.VALUE_SUCCESS)

В строках 5 и 6 вызывается процедура модуля Module2 (строки 26-28), где и происходит фактическое построение группового делегата. Это возможно благодаря тому, что делегат передается по ссылке, а не по значению. Обратите внимание на преобразование типа метода Combine к типу делегата в строке 27. Непосредственный вызов функций группового делегата происходит в строках 30-35. Всем зарегистрированным функциям передается значение перечисляемого типа RETURNJALUES . VALUE_SUCCESS. Результат выполнения программы показан на рисунке.

Групповые делегаты как члены классов

В предыдущем примере все модули имеют доступ ко всем функциям остальных модулей. Такую архитектуру нельзя признать удачной — правильнее было бы оформить делегат в виде члена класса, нежели в виде открытого объекта. Это позволит выполнить перед его созданием проверку, аналогичную той, которая выполняется для других членов класса. Ниже приведен слегка измененный вариант предыду-

щей архитектуры, где перед дополнением группового делегата новыми функциями выполняется проверка (в данном примере — весьма тривиальная). Соответствующий фрагмент выделен жирным шрифтом:

Option Strict On

Public Class DelegateServer

Public Delegate Sub ClientCallback(ByVal IngVal As Long)

Private m_Clients As ClientCallback

‘ Использовать конструктор по умолчанию

Public Sub RegisterDelegate(ByVal aDelegate As

ClientCallback.ByVal dolt As Boolean)

‘ Обычно здесь выполняется полноценная проверка.

‘ В данном примере функция обратного вызова регистрируется

‘ лишь в том случае, если второй параметр равен

True. If dolt Then

m_Clients = CType(System.Delegate.Combine(m_ Clients.aDelegate)._

Public Sub CallClients(ByVal IngVal As Long)

Dim delsrv As New DelegateServer()

‘ He вызывается — второй параметр равен False!

‘ Инициировать обращение к клиентам

Console.WriteLine(«Press enter to end.»)

Public Sub DelegateCallbackHandlerKByValIngVal As Long)

Public Sub DelegateCallbackHandler2(ByVal IngVal As Long)

System.Console.Wri teLine(«DelegateCal1backHandler2 cal1ed»)

Делегаты и события

Мы рассмотрели разнообразные примеры использования делегатов, однако ни один из них не имел отношения к обработке событий. Впрочем, связь между делегатами и событиями в VB .NET весьма проста. При каждом использовании сокращенного синтаксиса обработки событий, описанного в первой половине главы, VB .NET незаметно определяет класс делегата для обработки события, а команда AddressOf создает экземпляр делегата для этого обработчика. Например, следующие две строки эквивалентны (EventHandler — имя неявно определяемого делегата):

AddHandler Buttonl.Click.AddressOf Me.Buttonl_Click

AddHandler Buttonl.Click.New EventHandler(AddressOf Buttonl Click)

В сущности, каждое событие соответствует делегату следующего вида:

Public Delegate Event (sender As Object.evt As EventArgs)

Вызов RaiseEvent просто приводит к вызову Invoke для автоматически сгенерированного делегата.

НОВОСТИ ФОРУМА
Рыцари теории эфира
01.10.2020 — 05:20: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]69vJGqDENq4[/Youtube][/center]
[center]14:36[/center]
Osievskii Global News
29 сент. Отправлено 05:20, 01.10.2020 г.’ target=_top>Просвещение от Вячеслава Осиевского — Карим_Хайдаров.
30.09.2020 — 12:51: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Ok]376309070[/Ok][/center]
[center]11:03[/center] Отправлено 12:51, 30.09.2020 г.’ target=_top>Просвещение от Дэйвида Дюка — Карим_Хайдаров.
30.09.2020 — 11:53: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]VVQv1EzDTtY[/Youtube][/center]
[center]10:43[/center]

интервью Раввина Борода https://cursorinfo.co.il/all-news/rav.
мой телеграмм https://t.me/peshekhonovandrei
мой твиттер https://twitter.com/Andrey54708595
мой инстаграм https://www.instagram.com/andreipeshekhonow/

[b]Мой комментарий:
Андрей спрашивает: Краснодарская синагога — это что, военный объект?
— Да, военный, потому что имеет разрешение от Росатома на манипуляции с радиоактивными веществами, а также иными веществами, опасными в отношении массового поражения. Именно это было выявлено группой краснодарцев во главе с Мариной Мелиховой.

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

Реализация обработки событий на C++

Событием (event) называется исходящий вызов. Этот термин, наверное, хорошо знаком тем, кто работает с такими языками, как Delphi, Visual Basic и т.д. При возникновении события происходит вызов всех его обработчиков. Так как объект-инициатор события может ничего не знать об обработчиках, то событие называют исходящим вызовом. Работа события происходит по принципу «от одного объекта к нескольким». Важно отметить, что событие (event) и сообщение (message) это разные понятия. Сообщением называется прямой вызов, который передаётся от объекта к объекту. То есть у сообщения имеется один обработчик.

События применяются довольно широко. Примером могут служить всевозможные библиотеки, реализующие графический интерфес пользователя. Но события при правильном применении могут оказаться ДЕЙСТВИТЕЛЬНО ПОЛЕЗНОЙ ВЕЩЬЮ К сожалению исторически сложилось так, что в C++ нет событий. Поэтому при необходимости разработчики реализуют их на уровне библиотеки. Здесь вашему вниманию представлена реализация одной такой библиотеки. В ней есть два класса: Delegate и Event. .. далее

Илон Маск рекомендует:  Функции баз данных sesam
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL