Introduction
Delegates are one of the wonderful features of .Net Framework. Their primary use is focused on support event handling and callback mechanism. In this article, we will explore how to use delegate as callback support to make simple communication between different opened windows forms.
In this article, I assume that you know the definition of delegate and its primary use in .Net Framework.
Scenario overview
In many scenarios, a windows form is used to open some other windows forms as modal or non-modal. The newly opened form or the opener itself (the parent form) may need to send each other some notifications or values. For example, you select a value from a ComboBox on the newly opened form, and you want to send this value immediately on select to the opener parent form. Or you changed a parameter value in the opener form (parent) that the newly opened form depends on. Such scenarios can be implemented using delegates in form of callbacks.
Scenario in action
We are going to explore 2 scenarios:
- First Scenario: The main windows form (parent) that can open deferent instances of other windows forms (children).
The parent form will contain a TextBox, when the text in this text box changes, all opened windows forms (children) should be notified with this change and display it.
- Second Scenario: The main windows form (parent) that contains a ListBox will open a dialog form that contains a TextBox with two buttons (Add Item) & (Close). Whenever the user click the Add Item button, the item should be added to the ListBox in the main form without closing the dialog. Close button should close the dialog.
Of course these scenarios can be done by exposing controls as public instances. But many would prefer to keep controls as private and start using delegates
The Rule
First of all, you need to decide what do you want to send to the other form cause this will lead you to decide what is parameter type you will use in your delegate.
In our case here, we need only to pass string, so we need to define delegate with string as parameter for example:
public delegate void SetParameterValueDelegate(string value);
Second, you need to decide who is the owner of the communication, in other words, which form will send the notification?! In our first scenario the main form (parent) will be the owner of the communication. But in the second scenario the dialog form will be the owner.
Once you decide the owner, you’ll need to declare an instance of your delegate type inside the owner class for example:
public SetParameterValueDelegate SetParameterValueCallback;
The other forms will be subscribers for the callback (delegate), only if you wish to send notification to them.
The First Scenario
The following screenshot shows the first scenario in action, just to note the simple user interface we are using to demonstrate the example:
You need to declare a delegate, should be outside any class for our example needs.
Listing 01-Declare a delegate:
public delegate void SetParameterValueDelegate(string value);
The following is the code written in the Main From «FrmMain Class»:
Listing 02-FrmMain Class:
//Declare delagete callback function, the owner of communication
public SetParameterValueDelegate SetParameterValueCallback;
private void btnOpenFrm1_Click(object sender, EventArgs e)
{
FrmChild1 frm = new FrmChild1();
//Subscribe frm for Callback
this.SetParameterValueCallback += new SetParameterValueDelegate(frm.SetParamValueCallbackFn);
frm.Show();
}
private void btnOpenFrm2_Click(object sender, EventArgs e)
{
FrmChild2 frm = new FrmChild2();
//Subscribe frm for Callback
this.SetParameterValueCallback += new SetParameterValueDelegate(frm.SetParamValueCallbackFn);
frm.Show();
}
private void txtParam_TextChanged(object sender, EventArgs e)
{
//Send Notification to all subscribers
SetParameterValueCallback(txtParam.Text);
}
Did you note the frm.SetParamValueCallbackFn?! Well this is method defined in both FrmChild1 & FrmChild2 forms, of course the method can have different names on each class.
What you should know and note, this method should have the same signature as the delegate, which means, the delegate is void, your method should be void, the delegate accepts only one string parameter, your method also should accept only one string parameter.
The following shows the definition of the SetParamValueCallbackFn method:
Listing 03-SetParamValueCallbackFn Method:
public void SetParamValueCallbackFn(string param)
{
txtParam.Text = param;
}
The Second Scenario
You might guess what we should do in this scenario, first we will create a new delegate that takes a string parameter, we will call it AddItemDelegate. In the Dialog form (FrmDialog) we will declare a callback method of type AddItemDelegate. In main from (FrmMain) we will define the callback function.
Listing 04-AddItemDelegate:
public delegate void AddItemDelegate(string item);
Listing 05-FrmDialog Class:
//Declare delagete callback function, the owner of communication
public AddItemDelegate AddItemCallback;
private void btnAdd_Click(object sender, EventArgs e)
{
//Notification subscribers
AddItemCallback(txtItem.Text);
}
Listing 06-FrmMain Class:
private void btnScenario2_Click(object sender, EventArgs e)
{
FrmDialog dlg = new FrmDialog();
//Subscribe this form for callback
dlg.AddItemCallback = new AddItemDelegate(this.AddItemCallbackFn);
dlg.ShowDialog();
}
private void AddItemCallbackFn(string item)
{
lstBx.Items.Add(item);
}
Conclusion
The way of using delegates to send information between windows forms is much more clear than exposing controls as public instances, well this is my opinion.
In the examples represented here, we could use on delegate instead of tow, but I represented this just for clarification. Hope you find it useful.
Делегаты и события
Делегаты являются типом, реализующим механизм динамического вызова функций с разными именами, но одинаковой сигнатурой и типом возвращаемого значения. Это еще один подход к полиморфизму в объектно-ориентированном программировании. Делегат аналогичен указателю на функцию в C / C++.
Делегат объявляется как тип в области видимости пространства имен с помощью ключевого слова delegate по следующему синтаксису
delegate тип_возврата имя_типа_делегата(список_параметров);
Внутри вызывающего кода создается экземпляр делегата, конструктору которого в качестве аргумента передается имя адресуемого метода с тождественной сигнатурой и типом возвращаемого значения
имя_типа_делегата объект = new конструктор_делегата(имя_метода);
После создания экземпляра делегата для конкретного метода вызовы метода можно выполнять через объект делегата
объект_делегата(список_параметров);
вместо
имя_метода(список_параметров);
Все делегаты представляют собой типы, которые неявным образом выводятся из класса System.Delegate, но напрямую так не объявляются.
Приведем поясняющий пример. Предположим, что нужно создать класс для выполнения операций сложения и вычитания двух целых чисел. Вот код этого класса
using System;
using System.Windows.Forms;
namespace MyApp
{
// Объявление своего типа класса
class MyClass
{
string str;
public void Add(int x, int y)
{
str = String.Format("{0} + {1} = {2}", x, y, x + y);
MessageBox.Show(str, "Сложение целых чисел");
}
public void Subtract(int x, int y)
{
str = String.Format("{0} - {1} = {2}", x, y, x - y);
MessageBox.Show(str, "Вычитание целых чисел");
}
}
}
Листинг
6.22 .
Код файла MyClass.cs
Теперь создадим вызывающую сторону кода класса, в которой предусмотрим
- Вызов методов напрямую
- Одиночные вызовы через экземпляры делегата
- Организацию цепочки вызовов (многоадресный вызов) через объект делегата
Вот этот код
// Удалили сокращения пространств имен
// Все используемые типы находятся в проекте
namespace MyApp
{
// Объявление своего типа делегата
delegate void Arithmetics(int a, int b);
class EntryPoint
{
static void Main()
{
// Объявляем ссылочную переменную типа делегата
Arithmetics obDelegate;
// Объявляем ссылочную переменную типа класса
MyClass ob;
// Создаем объект класса с методами
ob = new MyClass();
// Заготавливаем передаваемые методам аргументы
int x = 5, y = 10;
// Прямой вызов методов объекта класса
ob.Add(x, y);
ob.Subtract(x, y);
// Косвенный (через делегата) вызов методов объекта класса
// Адресуемся к первому методу объекта класса
obDelegate = new Arithmetics(ob.Add);
obDelegate(x, y); // Вызываем метод через объект делегата
// Адресуемся ко второму методу объекта класса
obDelegate = new Arithmetics(ob.Subtract);
obDelegate(x, y); // Вызываем метод через объект делегата
// Использование делегата для организации цепочки вызовов
obDelegate = new Arithmetics(ob.Add);
obDelegate += new Arithmetics(ob.Subtract);
obDelegate(x, y); // Вызов методов
}
}
}
Листинг
6.23 .
Код файла Program.cs
При запуске приведенного кода трижды повторяется результат сложения и вычитания двух целых чисел, который инициируется вызовами методов напрямую, одиночными вызовами через делегата и множественным вызовом через делегата. Множественная адресации возможна только для функций, возвращающих тип void, одиночная адресация возможна для любого возвращаемого типа.
Способность делегатов к множественной адресации однотипных функций (одинаковой сигнатуры) является наиболее ценным их качеством. Но рассмотренный способ применения делегатов используется не очень часто. Большее распространение получило применение делегатов для создания событий. События являются современным механизмом общения классов и объектов приложения между собой. Один объект может сгенерировать событие и передать вместе с ним требуемые значения аргументов. Другие объекты, которые подписались на это событие и ждут его появления, реагируют на него вызовом соответствующих обработчиков.
Сгенерированное объектом событие помещается в канал сообщений, созданный для приложения операционной системой, и предлагается для обработки всем подписавшимся на него объектам. Подписка объекта на событие означает существование в объекте обработчика этого события. Хотя может быть и такое, что обработчик как метод создан в объекте, но не подключен на прослушивание соответствующего события, т.е. не зарегистрирован в канале сообщений.
Поскольку событие представляет собой косвенный вызов методов (обработчиков) всех подписавшихся на него объектов и может передавать из сгенерировавшего его объекта фактические значения аргументов, то обработчики всех подписавшихся объектов должны иметь одинаковую сигнатуру. Вот здесь-то и нашли широкое применение делегаты, с помощью которых и определяется как сигнатура самого события, так и сигнатура подписавшихся на него обработчиков.
В отличие от делегатов, которые являются типами и объявляются непосредственно в пространстве имен, события являются членами классов и объявляются внутри классов как экземпляры делегатов с ключевым словом event. Чтобы событие было видно в других классах, оно должно быть общедоступным ( public ) или защищенным ( protected ). События как члены класса могут наследоваться другими классами точно также, как и другие доступные члены класса (методы, свойства, поля).
Приведем пример. Пусть гаишник стоит на дороге, измеряет скорость, оценивает водителя и по рации передает свое решение на пост писарю и кассиру. Писарь заполняет протокол на штраф или премию, а кассир берет или выдает деньги. Каждый из них самостоятельно определяет, кому что делать, в зависимости от поступившего сообщения. Бедному водителю остается только бодро рапортовать.
Вот код примера
using System;
using System.Windows.Forms;
using System.Drawing;
namespace MyApp
{
// Объявление с помощью делегата прототипов событий и обработчиков
delegate void Premium(Driver driver, int speed);
delegate void Penalty(Driver driver, int speed);
// Гаишник
class Gai
{
// Объявление событий и критерия оценки
public static event Premium premium;
public static event Penalty penalty;
const int SPEED_BORDER = 60; // Критерий оценки
// Измерить скорость и сгенерировать событие
public static void MeasureSpeed(Driver driver, int speed)
{
// Критерий выполнен и обработчик существует
if (speed <= SPEED_BORDER && premium != null)
premium(driver, speed);
else if(penalty != null)
penalty(driver, speed);
}
}
// Писарь
//class Писарь
class Clerk
{
// Вспомогательное поле
string message;
// Конструктор для автоматической подписки
//public Писарь()
public Clerk()
{
// Пописка на события
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
}
void Gai_penalty(Driver driver, int speed)
{
message = "Оштрафовать водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Писарь");
}
void Gai_premium(Driver driver, int speed)
{
message = "Поощрить водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Писарь");
}
}
// Кассир
class Cashier
{
// Вспомогательное поле
string message;
// Конструктор для автоматической подписки
public Cashier()
{
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
}
void Gai_penalty(Driver driver, int speed)
{
message = "Взымаю штраф с водителя ";
message += driver.Name + "а за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Кассир");
}
void Gai_premium(Driver driver, int speed)
{
message = "Выдаю премию водителю ";
message += driver.Name + "у за скорость ";
message += speed + " км/час";
MessageBox.Show(message, "Кассир");
}
}
// Водитель
class Driver
{
public string Name;
public Driver(string Name)
{
Gai.penalty += new Penalty(Gai_penalty);
Gai.premium += new Premium(Gai_premium);
this.Name = Name;
}
void Gai_penalty(Driver driver, int speed)
{
if (driver == this)
MessageBox.Show("Признателен за штраф!!!", "Водитель " + Name);
}
void Gai_premium(Driver driver, int speed)
{
if (driver == this)
MessageBox.Show("Благодарю за премию!!!", "Водитель " + Name);
}
}
// Для запуска
class ExecuteClass
{
public ExecuteClass()
{
// Создаем объекты
//Писарь писарь = new Писарь();
// Если событие принимают сразу несколько объектов,
// то порядок обработки событий определяется
// порядком создания принимающих объектов!!!
Clerk clerk = new Clerk();
Cashier cashier = new Cashier();
Driver driver1 = new Driver("Иванов");
Driver driver2 = new Driver("Петров");
Driver driver3 = new Driver("Сидоров");
// Измерить скорость и передать объектам
// готовое решение с дополнительной информацией
Gai.MeasureSpeed(driver1, 50);
Gai.MeasureSpeed(driver2, 70);
Gai.MeasureSpeed(driver3, 90);
}
}
}
Листинг
6.24 .
Код файла AppClasses.cs
// Удалили сокращения пространств имен
// Все используемые типы находятся в проекте
namespace MyApp
{
class EntryPoint
{
static void Main()
{
new ExecuteClass();
}
}
}
Листинг
6.25 .
Код файла Program.cs
Обратите внимание на закомментированное имя типа «Писарь» и ссылку «писарь». Их можно вполне использовать как идентификаторы переменных и пользовательских типов. Visual Studio 2005 это позволяет, но сообщество программистов не приветствует — непрофессионально!
В приведенном коде использован распространенный способ вызова методов разных объектов с помощью событий. Объект, сгенерировавший событие, не заботится о том, будет ли оно обработано другими объектами. Он просто констатирует возникшую ситуацию выбросом сообщения. Если другой объект заинтересован в получении этого сообщения, он должен на него подписаться с регистрацией обработчика. Простая, но очень эффективная схема взаимодействия объектов.
Здесь есть одно обстоятельство. Если объект сгенерировал событие, но к нему не было присоединено ни одного обработчика, то будет сгенерировано исключение. Поэтому мы сделали предварительную проверку ссылки события на нулевое значения.
This article is aimed at providing some simple methods for passing data between forms in Windows applications
- Download demo project — 51.01 KB
Passing Data Between Forms
Introduction
Some of you would have faced a scenario where you wanted to pass data from one form to another in WinForms. Honestly, I too had a similar problem (that’s why I am writing this article!).
There are so many methods (How many? I don’t know) to pass data between forms in Windows application. In this article, let me take four important (easiest) ways of accomplishing this:
- Using constructor
- Using objects
- Using properties
- Using delegates
Let us see all the above methods in detail in the following sections.
For data to be passed between forms using any of the above methods, we need two forms and some controls. Let us start by following the steps given below.
Step 1
Create a new project and select Windows application. This will create a default form as “Form1”. We can use this form for sending data.
Step 2
Add a textbox and a button to the form.
Step 3
Add another Windows Form for receiving the data and to display it. Right click the project and select Add->Add Windows Form. Enter a name or use the default name “Form2.cs” and click ok button.
Step 4
Add a label to the second form to display the text from form1.
The Constructor Approach
This could be the easiest method of all. A method is invoked whenever you instantiate an object. This method is called a constructor. Code a constructor for form2 class with one string parameter. In the constructor, assign the text to the label’s text property. Instantiate form2 class in form1’s button click event handler using the constructor with one string parameter and pass the textbox’s text to the constructor.
Follow the steps given below:
Step 1
Code a constructor for form2 class as below:
public Form2(string strTextBox)
{
InitializeComponent();
label1.Text=strTextBox;
}
Step 2
Instantiate form2 class in form1’s button click event handler as below:
private void button1_Click(object sender, System.EventArgs e)
{
Form2 frm=new Form2(textBox1.Text);
frm.Show();
}
The Object Approach
Objects are reference types, and are created on the heap, using the keyword new. Here we are going to pass data using objects. The approach is simple; in form2 we are going to instantiate form1 class.
Then instantiate form2 in the button click event handler of form1. After this we are going to pass form1 object to the form2 using form2’s form1 object. The last step is to invoke the form2 window by calling the form2’s show method.
Follow the below steps:
Step 1
Change the access modifier for textbox in form1 to public:
public class Form1 : System.Windows.Forms.Form
{
public System.Windows.Forms.TextBox textBox1;
Step 2
In the button click event-handler, add the following code:
private void btnSend_Click(object sender, System.EventArgs e)
{
Form2 frm= new Form2();
frm.frm1=this;
frm.Show();
}
Step 3
In form2.cs, instantiate form1 class:
public class Form2 : System.Windows.Forms.Form
{
private System.Windows.Forms.Label label1;
public Form1 frm1;
Step 4
In Form2’s Load method, type cast the object (frm1) of form1 to Form1 and access form1’s textbox and assign its text to label’s text.
private void Form2_Load(object sender, System.EventArgs e)
{
label1.Text=((Form1)frm1).textBox1.Text;
}
The Properties Approach
Properties allow clients to access class state as if they were accessing member fields directly, while actually implementing that access through a class method. In this method, we are going to add one property to each form. In form1 we are going to use one property for retrieving value from the textbox and in form2, one property to set the label’s text property. Then, in form1’s button click event handler, we are going to instantiate form2 and use the form2’s property to set the label’s text.
Follow the below steps:
Step 1
Add a property in form1 to retrieve value from textbox:
public string _textBox1
{
get{return textBox1.Text;}
}
Step 2
Add a property in form2 to set the labels’ text:
public string _textBox
{
set{label1.Text=value;}
}
Step 3
In form1’s button click event handler, add the following code:
private void button1_Click(object sender, System.EventArgs e)
{
Form2 frm=new Form2();
frm._textBox=_textBox1;
frm.Show();
}
The Delegates Approach
Technically, a delegate is a reference type used to encapsulate a method with a specific signature and return type. You can encapsulate any matching method in that delegate. Here we are going to create a delegate with some signature and assign a function to the delegate to assign the text from textbox to label.
Follow the below steps:
Step 1
Add a delegate signature to form1 as below:
public delegate void delPassData(TextBox text);
Step 2
In form1’s button click event handler, instantiate form2 class and delegate. Assign a function in form2 to the delegate and call the delegate as below:
private void btnSend_Click(object sender, System.EventArgs e)
{
Form2 frm= new Form2();
delPassData del=new delPassData(frm.funData);
del(this.textBox1);
frm.Show();
}
Step 3
In form2, add a function to which the delegate should point to. This function will assign textbox’s text to the label:
public void funData(TextBox txtForm1)
{
label1.Text = txtForm1.Text;
}
Conclusion
These four approaches are very simple in implementing data passing between forms. There are also other methods available in accomplishing the same. Source code for the methods I stated above is given at the top for download. It is time for you to put on your thinking cap and find other ways of doing this. Happy coding!!!
History
- 16th May, 2006: Initial post
В Заметке о консольных и оконных (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: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.
Three methods are introduced in the project
###Method 1: call its method to control the subform by saving the reference of the object;
###Method 2: through delegation, assign value to the delegate before the sub form is displayed, and pay attention to the data change of the main window. When there are multiple forms that need to receive information, only continue to assign value (+ =) to the delegate, which realizes the decoupling of data transmission;
###Method 3: register events and pay attention to the change of the main window message before the sub form pops up. When there are multiple forms that need to receive information, we just need to register data receiving events for the forms respectively, which realizes the decoupling of data transmission;
Methods 2 and 3 are publish subscribe mode (observer mode) — I am also a beginner of design pattern. If you have any questions, please email me, thank you;
The interface of the demo form is as follows:
Open A and B forms in MainForm, input text data in MainForm, click send message, and the corresponding data will be displayed in text boxes of A and B;
The main window is the publisher of the message, and forms A, B, etc. are the receiver of the message;
Part of the code is as follows (all source code refer to the above link)
1. Part of the code of the main form:
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10
11 namespace WinFrmDemo
12 {
13
14
15 public partial class MainForm : Form
16 {
17 #region method 1 (not recommended) -- the public method of the object called by saving the reference of the object realizes the value transfer of the form
18 //When the number of windows receiving data increases, the code of sending message needs to be modified, and the corresponding number of form references are added. The scalability is poor and the coupling is high
19 //public ObeserverFormA ChildFormA { get; set; }
20 //public ObeserverFormB ChildFormB { get; set; }
21 #endregion
22
23 #region method 2 --- value transfer by delegation
24 //The delegate that defines the publish message is a type delegate that can be executed externally
25 public Action<string> SendMsg { get; set; }
26 #endregion
27
28 #region method 3 (recommended) -- event mode
29 //Add event keyword
30 //The event that defines a message publication is a special instance of a delegate. An event can only trigger execution within a class
31 public event EventHandler SendMsgEvent; //Use the default event handling delegate
32 #endregion
33
34
35
36 public MainForm()
37 {
38 InitializeComponent();
39 }
40
41 private void ParentFrm_Load(object sender, EventArgs e)
42 {
43
44 #region method 1 (not recommended)
45 //ObeserverFormA childFormA = new ObeserverFormA();
46 //ChildFormA = childFormA;
47 //childFormA.Show();
48 //ObeserverFormB childFormB = new ObeserverFormB();
49 //ChildFormB = childFormB;
50 //childFormB.Show();
51 #endregion
52
53 #region method 2 --- value transfer by delegation
54 //Before the child form pops up, assign a value to the delegate, and pay attention to the change of the main window message. When there are multiple forms that need to receive information, you just need to modify it here
55 //ObeserverFormA childFormA = new ObeserverFormA();
56 //SendMsg += childFormA.SetText ; / / delegate assignment
57 //childFormA.Show();
58 //ObeserverFormB childFormB = new ObeserverFormB();
59 //SendMsg += childFormB.SetText;
60 //childFormB.Show();
61 #endregion
62
63
64 #region method 3 (recommended) -- event mode
65 //Before the subform pops up, register the event and pay attention to the change of the main window message. When there are multiple forms that need to receive information, just modify it here
66 ObeserverFormA childFormA = new ObeserverFormA();
67 SendMsgEvent += childFormA.MainFormTxtChaned;//Register events for the subform and set the text in the event handling code in the subform
68 childFormA.Show();
69 ObeserverFormB childFormB = new ObeserverFormB();
70 SendMsgEvent += childFormB.MainFormTxtChaned;
71 childFormB.Show();
72 #endregion
73
74
75
76 }
77
78 //When inputting text in MainForm and clicking send message, the text box of subform displays the data of main form
79 private void btnSendMsg_Click(object sender, EventArgs e)
80 {
81 #region method 1 (not recommended)
82
83 //ChildFormA.SetText(this.txtMsg.Text);
84 //ChildFormB.SetText(this.txtMsg.Text);
85
86 #endregion
87
88 #region method 2 --- value transfer by delegation
89 //if (SendMsg!=null)
90 //{
91 // SendMsg( this.txtMsg.Text ); / / performs all registered delegates
92 //}
93
94 #endregion
95
96 #region method 3 (recommended) -- event mode
97 //Trigger event
98 //EventArgs: write a subclass to inherit the class, and add the data information to be encapsulated in the subclass. Only string information needs to be passed here. See MyEventArgs for details
99 SendMsgEvent(this,new MyEventArg(){Text=this.txtMsg.Text});
100 #endregion
101 }
102 }
103 }
2. Part A code of subform
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10
11 namespace WinFrmDemo
12 {
13 public partial class ObeserverFormA : Form
14 {
15 /// <summary>
16 ///Provides external access to its own elements
17 /// </summary>
18 /// <param name="txt"></param>
19 public void SetText(string txt)
20 {
21 this.txtMsg.Text = txt;
22 }
23 public ObeserverFormA()
24 {
25 InitializeComponent();
26 }
27
28 public void AfterParentFrmTextChange(object sender, EventArgs e)
29 {
30 //Get the text from the parent form
31 MyEventArg arg = e as MyEventArg;
32 this.SetText(arg.Text);
33 }
34
35 internal void MainFormTxtChaned(object sender, EventArgs e)
36 {
37 //Get the text from the main form
38 MyEventArg arg = e as MyEventArg;
39 this.SetText(arg.Text);
40
41 }
42 }
43 }
3. Part of the code for subform B
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10
11 namespace WinFrmDemo
12 {
13 public partial class ObeserverFormB : Form
14 {
15
16 public ObeserverFormB()
17 {
18 InitializeComponent();
19 }
20
21 /// <summary>
22 ///Provides external access to its own elements
23 /// </summary>
24 /// <param name="txt"></param>
25 public void SetText(string txt)
26 {
27 this.txtMsg.Text = txt;
28 }
29
30 internal void MainFormTxtChaned(object sender, EventArgs e)
31 {
32 //Get the text from the main form
33 MyEventArg arg = e as MyEventArg;
34 this.SetText(arg.Text);
35 }
36 }
37 }
All the principles are interlinked. What we do is not creative work. All the problems have been solved by predecessors, so we are more fearless. What’s more, we not only have bookstores, but also have the Internet. We can find the information we need with our hands and feet. We just need to study it carefully. So when you encounter difficulties, please calm down and study slowly, because as long as you are attentive, there is nothing you can’t learn.
Add:
Defined in the same project as the form MyEventArgs.cs file
The contents of the document are as follows
************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFrmDemo
{
public class MyEventArg :EventArgs
{
//Transfer the data information of the main form
public string Text { get; set; }
}
}
************
This paper is reproduced from: https://www.cnblogs.com/codeToUp/p/5371062.html
