В Заметке о консольных и оконных (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>. В данном случае тип TEventArgs обозначает тип аргумента, передаваемого параметру EventArgs события.
Для обработки многих событий параметр типа EventArgs оказывается ненужным. Поэтому с целью упрощения создания кода в подобных ситуациях в среду .NET Framework и был внедрен необобщенный делегат типа EventHandler, используемый для объявления обработчиков событий, которым не требуется дополнительная информация о событиях (см. наш первый пример).
Пример использования обобщенного делегата EventHandler<TEventArgs>
Обобщенный делегат EventHandler<MyEA> используется для
объявления события Ez:
public event EventHandler<MyEA> Ez;
Аргументы, передаваемые в метод, задаются в классе MyEA, который наследуется от класса EventArgs.
Постановка задачи «Управление размерами и цветом формы»
Набор цветов: Red, Green, Blue, Yellow + исходный (добавляйте любые!)
Размеры: 500х150, 550×200, 600×250, 650×300
Элементы управления:
Кнопка button1 — Разрешение/Запрет изменение свойств формы
Кнопка button2 — Перекраска формы в желтый цвет без ограничений
Элемент comboBox1 — для выбора цвета: Red, Green, Blue, прежний
Метка label1 — подсказка: «Выберите цвет закраски формы» к comboBox1.
Начальная форма может выглядеть так:
Создаются два класса:
1) Класс Моих Событий Аргументы:
class MyEA : EventArgs // с полями (добавляйте любые) : { public char ch; // буква цвета public int htw; // высота формы }
2) Мой класс Обработка события:
class MyEH { public event EventHandler<MyEA> Ez; // мое событие public void OnEz(MyEA c) // и его метод }
Далее приводится текст файла Form1.cs с комментариями:
using System; using System.Drawing; using System.Windows.Forms; namespace ОбобщенныйДелегатЦвет { public partial class Form1 : Form { public static bool flag = false; // Запретить/разрешить смену цвета char []symb = {'R','G','B','X'}; // массив символов, см.comboBox1 int[] heigth = { 150, 200, 250, 300 }; public Form1() { InitializeComponent(); } // Переключатель флага: Разрешить/Запретить смену цвета формы private void button1_Click(object sender, EventArgs e) { if (flag) { flag = false; button1.Text = "Изменение цвета формы разрешить!"; } else { flag = true; button1.Text = "Изменение цвета формы запретить!"; } } // Изменение цвета (4 цвета) и размеров формы private void ChangeBackColor(object sender, MyEA e) { switch (e.ch) { case 'R': { this.BackColor = Color.Red; this.Height = heigth[0]; this.Width = 350 + heigth[0]; break; } case 'G': { this.BackColor = Color.Green; this.Height = heigth[1]; this.Width = 350 + heigth[1]; break; } case 'B': { this.BackColor = Color.Blue; this.Height = heigth[2]; this.Width = 350 + heigth[2]; break; } case 'Y': { this.BackColor = Color.Yellow; this.Height = heigth[3]; this.Width = 350 + heigth[3]; break; } default: // оставить без изменений break; }; // end switch } // Реакция на изменение выбора цвета формы private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { MyEH met = new MyEH(); // объект класса Моя обработка событий // Получить аргументы: букву цвета и высоту формы - в объект e1: int i = comboBox1.SelectedIndex;// номер цвета в массиве symb[] MyEA e1 = new MyEA(); // объект класса Мои Аргументы Событий e1.ch = symb[i]; // буква цвета e1.htw = heigth[i]; // высота формы if (flag) met.Ez += ChangeBackColor; // Событие Ez связать с методом else met.Ez -= ChangeBackColor; // Или разорвать связь // вызов метода обработки события, связанного с met.Ez met.OnEz(e1); } // Покраска в желтый без условий private void button2_Click(object sender, EventArgs e) { MyEH met2 = new MyEH(); // новый объект-событие и пр. MyEA e2 = new MyEA(); // новый объект-аргументы: e2.ch = 'Y'; // первый e2.htw = heigth[3]; // второй met2.Ez += ChangeBackColor; // связывание события с методом // (его аргументы класса MyEA) met2.OnEz(e2); // вызов метода } } // end класса Form1 // Класс Моих Событий Аргументы class MyEA : EventArgs { public char ch; // буква цвета public int htw; // высота формы } // Мой класс Обработка события class MyEH { public event EventHandler<MyEA> Ez; // мое событие public void OnEz(MyEA c) // и его метод { if (Ez != null) // Есть ли событие? Ez(this, c); // Старт события } } // end класса } // end namespace!
Вид формы после перекраски:
Примеры
Более эффективное применение этого похода изложено в примере студента Александра «Компьютерный тренажер «Управление техническим объектом в реальном времени». Сценарный подход«.
Другой пример применения делегатов рассмотрен в посте «Делегаты и методы».
NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.
А, пример же про изменение формы, тогда хорошо, что размер меняется
И еще в
char []symb = {‘R’,’G’,’B’,’X’}; // массив символов, см.comboBox1
вместо ‘X’ должно быть ‘Y’.
Чтобы работали кнопки, надо в конструкторе в событиях соответствующих кнопок выбрать button1_Click (для button1) и button2_Click (для button2).
При нажатии кнопки «Перекрасить в желтый цвет!» уменьшается размер окна, становится не видно часть элементов.
Можно дважды нажать на форму, в Forms.cs в появившемся методе Form1_Load прописать
private void Form1_Load(object sender, EventArgs e)
{
this.MinimumSize = this.Size;
}
«Создаются два класса:
1) Класс Моих Событий Аргументы:
2) Мой класс Обработка события:»
А где они создаются?
Антон! В том же пространстве имен. Это может быть в отдельном файле, или как у меня в примере, после (или до, неважно) в файле Form1.cs. Смотрите текст примера.
Виталий, спасибо за внимательность. Поясню, почему не получается у Вас выбрать цвет:
1) comboBox1 — это элемент, в котором должен быть задан список. Напишите:
Red
Green
Blue
Yellow
или если захотите по-русски:
красный
зеленый
синий
желтый.
2) Проверьте, связано ли событие SelectedIndexChanged с обработчиком события combobox1_SelectedIndexChanged() — окно Свойства, закладка — События (Events).
3) В этом обработчике событий изменяется объект e1 класса MyEA (через свойство comboBox1.SelectedIndex): e1.ch = symb[i]; где i — индекс выделенного элемента.
4) А далее событие met.Ez связывается с методом ChangeBackColor().
В примере упомянутые операторы выделены красным цветом, но сам текст мною не был изменен.
Мне следовало бы все это описать более подробно, но я решил оставить исходный текст как есть, пометив только красным цветом некоторые операторы и написав комментарий. Запустите программу с учетом этого ответа.
Пример не работает. В comboBox1 нет выбора цветов.