6193

События в Windows. Генерация событий

Реферат

Информатика, кибернетика и программирование

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

Русский

2012-12-30

97.5 KB

6 чел.

События

Приложения Windows управляются сообщениями. Это значит, что приложение общается с Windows, a Windows — с приложением посредством предопределенных сообщений. Эти сообщения представляют собой структуры, содержащие различные порции информации, используемые приложением и самой Windows для того, чтобы определить, что нужно делать дальше. До появления таких библиотек, как MFC, и таких сред разработки, как Visual Basic, разработчику приходилось самому заботиться о том, как нужно обрабатывать приходящие сообщения. Visual Basic и .NET обертывают некоторые из этих входящих сообщений в нечто, называемое событиями. Если ваша программа должна реагировать на специфическое входящее сообщение, вы должны обработать соответствующее событие. Общий пример этого — когда пользователь щелкает на кнопке формы. При этом Windows посылает сообщение WM_MOUSECLICK обработчику событий кнопки (иногда называемому оконной процедурой, или WndProc). Для разработчиков .NET это выглядит как событие кнопки OnClick.

При разработке объектно-ориентированных приложений требуется другая форма взаимодействия объектов. Когда в одном из ваших объектов происходит что-то интересное, то, вероятно, другие объекты должны быть информированы об этом. И снова на помощь приходят события. Точно так же, как .NET Framework обертывает сообщения Windows в события, ваши приложения могут использовать события в качестве коммуникационных посредников между объектами.

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

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

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

Представление события с точки зрения получателя

Получателем (адресатом) события может выступать любое приложение, объект или компонент, который нуждается в уведомлении, когда что-то происходит. Если имеется получатель события, то естественно, должен существовать и его отправитель. Работа отправителя заключается в генерации события. Отправителем может быть либо другой объект, либо сборка вашего приложения, или же — в случае системного событий, таких как щелчок мыши или нажатие клавиши — отправителем будет исполняющая система .NET. Важно отметить, что отправитель события ничего не знает том, кто его получает. Это делает события чрезвычайно полезными.

Теперь где-то внутри получателя будет метод, который отвечает за обработку события. Этот обработчик событий будет запускаться всякий раз, когда возникает зарегистрированное для него событие. И здесь вступают в действие делегаты. Поскольку отправитель не имеет никакого представления о получателе (получателях), между ними не может быть никаких ссылок. В качестве посредника используются делегаты. Отправитель определяет делегат, который будет использован получателем. Получатель, регистрирует обработчик события. Процесс прикрепления обработчика к событию называется привязкой события. Простой пример привязки события Click поможет нам проиллюстрировать этот процесс.

Во-первых, создадим простое приложение Windows Forms. Перетащим кнопку с панели инструментов и поместим ее на форму. В окне свойств переименуем кнопку в btnOne. В редакторе кода добавим следующую строку в текст конструктора Forml:

btnOne.Click += new EventHandler(Button_Click);

Теперь все, что нужно сделать в Visual Studio после ввода операции += — это нажать клавишу <ТаЬ> несколько раз, и редактор выполнит для нас всю остальную работу. В большинстве случаев это подходит. Однако в нашем примере имя обработчика событий по умолчанию не используется, поэтому мы должны самостоятельно ввести текст.

При этом происходит вот что: мы сообщаем исполняющей системе, что когда возникает событие Click для кнопки btnOne, то должен быть выполнен метод Button_Click.  EventHandler — это делегат, который использует событие для назначения обработчика (Button_Click) событию (Click). Отметим, что мы использовали операцию += для добавления метода к списку делегатов. Все это значит, что событию можно назначить более одного обработчика. Поскольку это групповой делегат, к нему применимы все правила о добавлении множественных методов; однако нет никакой гарантии того, в каком порядке они будут вызваны. Продолжим дальше, перетащив в форму еще одну кнопку, и переименуем ее в btnTwo. Теперь свяжем событие Click кнопки btnTwo с тем же методом Button_Click, как показано ниже:

btnOne.Click += new EventHandler(Button_Click);

btnTwo. Click += new EventHandler (Button_Click);

Делегат EventHandler определен средой .NET Framework. Он находится в пространстве имен System, и все события, определенные в .NET Framework, используют его. Как уже упоминалось выше, список делегатов должен иметь одну и ту же сигнатуру. Очевидно, что это так же справедливо и для делегатов событий. Ниже показано определение метода Button_Click:

private void btnOne_Click(object sender, EventArgs e)

    {

}

Относительно этого метода следует отметить несколько важных моментов. Во-первых, он всегда возвращает void. Обработчики событий не могут возвращать значений. Далее скажем о параметрах. Пока используется делегат EventHandler, параметрами будут object и EventArgs. Первый параметр — это объект, который возбудил событие. В данном примере это будет либо btnOne, либо btnTwo — в зависимости от того, какая кнопка была щелкнута. Благодаря тому, что передается ссылка на объект, вызвавший событие, можно привязывать один и тот же обработчик к более чем одному объекту. Например, вы можете определить один обработчик щелчков для нескольких кнопок и определять, какая именно кнопка нажата, опрашивая первый параметр.

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

Так, событие MouseDown использует MouseDownEventArgs. Он содержит такие свойства, как кнопка мыши, которая была нажата, координаты X и Y указателя, а также другую информацию, связанную с событием. Обратите внимание на шаблон имени, заканчивающийся на EventArgs. Позднее мы увидим, как создавать и применять пользовательский объект на базе EventArgs.

Также стоит обратить внимание на имя метода. По установленному соглашению любой обработчик событий должен придерживаться стиля «именования объект_событие».

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

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

if (((Button)sender).Name == "btnOne")

  lblInfo.Text = "Нажата первая кнопка";

else

  lblInfo.Text = "Нажата вторая кнопка";

Отметим, что поскольку параметр sender отправлен как объект, его нужно присти к типу объекта, вызвавшего событие, в данном случае — к Button. В этом примере мы используем свойство Name для определения кнопки, вызвавшей событие. Однако вы можете использовать другие свойства. Удобно использовать в этом сценарии свойство Tag, поскольку оно может хранить все, что вы пожелаете в него поместить. Чтобы увидеть, как работают групповые возможности делегата, добавим еще один метод к событию Click кнопки bthTwo. Используем имя метода по умолчанию. Тело конструктора формы теперь будет выглядеть примерно так:

this.btnOne.Click += new System.EventHandler(this.btnOne_Click);

this.btnTwo.Click += new System.EventHandler(this.btnOne_Click);

this.btnTwo.Click += new System.EventHandler(this.btnTwo_Click);

Если мы позволим Visual Studio самостоятельно создать заготовку, то увидим следующий метод-в конце исходного файла. Однако вызов функции MessageBox потребуется добавить вручную:


private
 void btnTwo_Click(object sender, EventArgs e)

{

 MessageBox.Show("Это произошло только при событии щелчка на кнопке 2");

}

Если вернуться назад и использовать анонимные методы, то методы Button_Click и btnTwo_Click не понадобятся.

При запуске этого примера щелчок на кнопке btnOne изменит текст метки. Щелок на кнопке btnTwo не только изменит текст метки, но также отобразит MessageBox (окно сообщения). Напомним еще раз, что нет никаких гарантий того, что изменения текста метки произойдет до появления MessageBox, поэтому не пишите зависимый код в обработчиках.

Может показаться, что для того, чтобы во всем этом разобраться, необходимо изучить множество концепций, но на практике объем кодирования, которое нужно выполнить в приемнике, весьма невелик. Также следует иметь в виду, что писать приемники событий вам придется гораздо чаще, чем их отправители. По крайней мере, что касается пользовательского интерфейса Windows, Microsoft уже разработала все необходимые отправители событий, которые вам понадобятся (они закодированы в базовых классах .NET, в пространстве имен Windows.Forms).

Генерация событий

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

Пример будет включать форму, генерирующую событие, которое будет прослушиваться другим классом. Когда возникает событие, принимающий объект определяет, должен ли выполняться какой-то процесс и затем отменяет событие, если этот процесс продолжаться не может. Нашей целью в данном случае будет определение того, что число секунд текущего времени больше или меньше 30. Если количество секунд меньше 30, свойство устанавливается в значение строки, представляющей текущее время; если же количество секунд будет больше 30, событие отменяется, а строка времени очищается.

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

public delegate void ActionEventHandler(object sender,

                    ActionCancelEventArgs ev);

public static event ActionEventHandler Action;

Что же на самом деле происходит в этих двух строках кода? Во-первых, мы объявляем новый тип делегата — ActionEventHandler. Причина того, что нужно создать новый, а не использовать какой-то из предопределенных .NET Framework делегатов состоит в том, что мы будем применять пользовательский класс, унаследованный от EventArgs. Вспомните, что сигнатура метода должна соответствовать делегату. Теперь мы имеем делегат для использования; следующая строка в действительности определяет событие. В этом случае создается событие Action, а синтаксис определения события требует, чтобы был специфицирован делегат, который будет с этим событием ассоциирован. Можно использовать делегат, уже определенный .NET Framework. Существует около 100 классов, унаследованных от EventArgs, поэтому всегда можно выбрать подходящий. Но поскольку в этом примере применяется пользовательскиё класс-наследник EventArgs, то для него должен быть создан соответствующий делегат.

Этот новый класс, ActionCancelEventArgs, является наследником CancelEventArgs, который, в свою очередь, унаследован от EventArgs. CancelEventArgs добавляет свойство Cancel. Cancel имеет булевский тип и информирует объект-отправитель о том, что принимающий объект желает отменить или остановить обработку события. В классе ActionCancelEventArgs добавлено свойство Message.

Это строковое свойство, предназначенное для передачи текстовой информации о состоянии обработки события. Ниже показан код класса ActionCancelEventArgs.

public class ActionCancelEventArgs : System.ComponentModel.CancelEventArgs

 {

   string _msg = "";

   public ActionCancelEventArgs(): base()  {}

   public ActionCancelEventArgs(bool cancel): base(cancel)  {}

   public ActionCancelEventArgs(bool cancel, string message): base(cancel)

   {

     _msg = message;

   }

   public string Message

   {

     get {return _msg;}

     set {_msg = value;}

   }

 }

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

ActionCancelEventArgs cancelEvent = new ActionCancelEventArgs();

OnAction(this, cancelEvent);

Довольно-таки просто. Создаем экземпляр класса ActionCancelEventArgs и передаем его событию в качестве одного из параметров. Однако есть одна небольшая проблема. Что если событие пока нигде не будет использовано? Что если для события не определен ни один обработчик? Событие Action может быть равно null. Если вы попытаетесь возбудить такое событие, то получите исключение с null-ссылкой. Если вы захотите создать класс-наследник формы и использовать в качестве базовой форму, имеющую определенное в ней событие Action, то должны будете сделать кое-что еще при возбуждении события Action. Пока вам пришлось бы определять в унаследованной форме другой обработчик событий, чтобы сделать его доступным. Чтобы облегчить этот процесс, а также перехватывать ошибку нулевой ссылки, потребуется создать метод с именем OnEventName, где EventName — имя события. В нашем примере для этого предусмотрен метод OnAction. Ниже представлен его код:

protected void OnAction(object sender, ActionCancelEventArgs ev)

   {

     if(Action != null)

       Action(sender, ev);

   }

He так много, но обеспечивается все необходимое. Объявляя этот метод защищенным (protected), мы обеспечиваем доступ к нему только из классов-наследников. Здесь также присутствует проверка на null перед возбуждением события. Если придется наследовать новый класс, который будет содержать этот метод и событие, то нужно будет переопределить метод OnAction и в нем обращаться к событию. Чтобы сделать это, в перегрузке придется вызвать base. OnAction(). В противном случае событие не будет возбуждено. Это соглашение об именовании используется повсюду в .NET Framework и зафиксировано в документации по .NET SDK.

Обратите внимание на два параметра, которые передаются методу OnEvent(). Они должны показаться вам знакомыми, потому что это те же самые параметры, которые должны быть переданы событию. Если событие должно быть возбуждено из другого объекта, а не того, в котором определен этот метод, то придется создать средство доступа с модификатором internal или public, а не protected. Иногда имеет смысл объявить класс, который состоит только из объявлений событий и ничего больше, чтобы их можно было вызывать из других классов. Нам все еще понадобятся методы OnEventName. Однако в этом случае они могут быть статическими.

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

using System;

using System.IO;

using System.ComponentModel;

namespace SimpleEvent

{

 /// <summary>

 /// Summary description for BusEntity.

 /// </summary>

 public class BusEntity

{

   string _time = "";

 public BusEntity()

{

  Form1.Action += new

                Form1.ActionEventHandler(EventManager_Action);

    }

   private void EventManager_Action(object sender,

                          ActionCancelEventArgs ev)

   {

     ev.Cancel = !DoActions();

     if(ev.Cancel)

       ev.Message = "Неправильное время";

   }

   private bool DoActions()

   {

     bool retVal = false;

     DateTime tm = DateTime.Now;

     if(tm.Second < 30)

     {

       _time = "Текущее время " + DateTime.Now.ToLongTimeString();

       retVal = true;

     }

     else

       _time = "";

     return retVal;

   }

   public string TimeString

   {

     get {return _time;}

   }

 }

}

В конструкторе объявлен обработчик события Form.Action. Отметим сходство синтаксиса с ранее зарегистрированным событием Click. Поскольку для объявления события применяется тот же шаблон, синтаксис использования также согласован. Здесь стоит уточнить еще кое-что, а именно — как получить ссылку на событие Action без ссылки на Forml в классе BusEntity. Вспомним, что в классе Forml coбытие Action объявлено статическим. Это не требование, но существенно облегчает создание обработчика. Можно объявить событие общедоступным (public), но тогда придется ссылаться на экземпляр Forml.

Когда мы кодировали событие в конструкторе, то вызывали метод, добавленный в список делегатов Form_Action, в соответствии со стандартами именования. В обработчике должно быть принято решение о том, нужно ли отменять событие. Метод DoActions возвращает булевское значение на базе временного критерия, описанного выше. DoActions также устанавливает строке _time правильное значение.

Далее возвращенное DoActions значение устанавливается для свойства ActionCancelEventArgs  Cancel. Вспомните, что класс EventArg обычно не делают ничего помимо того, что сохраняет в себе информацию, которой обмениваются отправитель и получатель. Если событие отменено (ev.Cancel = true), то свойство Message также получает строковое значение, описывающее причину отмены события. Теперь, если мы снова посмотрим на код обработчика события btnRaise_Click, видим, как используется свойство Cancel:

private void btnRaise_Click(object sender, EventArgs e)

   {

     ActionCancelEventArgs cancelEvent =

                   new ActionCancelEventArgs();

     OnAction(this, cancelEvent);

     if(cancelEvent.Cancel)

       lblInfo.Text = cancelEvent.Message;

     else

       lblInfo.Text = _busEntity.TimeString;

}

Обратите внимание, что здесь создается объект ActionCancelEventArgs. Затем возбуждается событие Action с передачей ему вновь созданного объекта - ActionCancelEventArgs. Когда вызывается метод OnAction и возбуждается событие, то выполняется код обработчика события Action в объекте BusEntity. Если существуют другие объекты, зарегистрировавшие событие Action, их обработчики также выполняются. Следует иметь в виду, что если есть объекты, обрабатывающие это событие, то все они должны видеть объект ActionCancelEventArgs. Если необходимо узнать, какой именно объект отменил событие, и не сделали ли это сразу несколько объектов, то в составе класса ActionCancelEventArgs понадобится какая-то списочная структура.

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

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

Резюме

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

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

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

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

using System.IO;

namespace SimpleEvent

{

 /// <summary>

 /// Summary description for Form1.

 /// </summary>

 public class Form1 : System.Windows.Forms.Form

 {

   private System.Windows.Forms.Label lblInfo;

   private System.Windows.Forms.Button btnRaise;

   /// <summary>

   /// Required designer variable.

   /// </summary>

   private System.ComponentModel.Container components = null;

   BusEntity _busEntity = new BusEntity();

   public delegate void ActionEventHandler(object sender, ActionCancelEventArgs ev);

   public static event ActionEventHandler Action;

   public Form1()

   {

     //

     // Required for Windows Form Designer support

     //

     InitializeComponent();

 

     btnRaise.Click += new EventHandler(btnRaise_Click);

     

   }

   /// <summary>

   /// Clean up any resources being used.

   /// </summary>

   protected override void Dispose( bool disposing )

   {

     if( disposing )

     {

       if (components != null)

       {

         components.Dispose();

       }

     }

     base.Dispose( disposing );

   }

   #region Windows Form Designer generated code

   #endregion

   /// <summary>

   /// The main entry point for the application.

   /// </summary>

   [STAThread]

   static void Main()

   {

     Application.Run(new Form1());

   }

   

   

   private void btnRaise_Click(object sender, EventArgs e)

   {

     ActionCancelEventArgs cancelEvent = new ActionCancelEventArgs();

     OnAction(this, cancelEvent);

     if(cancelEvent.Cancel)

       lblInfo.Text = cancelEvent.Message;

     else

       lblInfo.Text = _busEntity.TimeString;

   }

   protected void OnAction(object sender, ActionCancelEventArgs ev)

   {

     if(Action != null)

       Action(sender, ev);

}

 }

 public class ActionCancelEventArgs : System.ComponentModel.CancelEventArgs

 {

 

   string _msg = "";

   public ActionCancelEventArgs()  : base()  {}

   public ActionCancelEventArgs(bool cancel) : base(cancel)  {}

   public ActionCancelEventArgs(bool cancel, string message) : base(cancel)

   {

     _msg = message;

   }

   public string Message

   {

     get {return _msg;}

     set {_msg = value;}

   }

 }

}


 

А также другие работы, которые могут Вас заинтересовать

3537. Проблема рассказчика у А.С. Пушкина и Н.В. Гоголя, признанных мастеров слова 477 KB
  Для ценителя и исследователя литературы различие категорий "автор", "образ автора", "лирический герой", "образ рассказчика" очевидно. И все же в нашей работе важно сразу определить данные понятия. Действительно, категории "образ автора", "л...
3538. Изучение аналого-цифрового преобразователя 417.5 KB
  Изучение аналого-цифрового преобразователя Изучить функционирование встроенного аналого-цифрового преобразователя (АЦП) микроконтроллера АТmega8535, получить практические навыки программирования микроконтроллера для обработки аналоговых сигналов. По...
3539. Информационные технологии в социально-культурном сервисе и туризме. 1.53 MB
  Информационные технологии в социально-культурном сервисе и туризме. Рассмотрены и проанализированы основные направления использования современных компьютерных технологий в социально-культурном сервисе и туризме. Показаны роль и влияние информационны...
3540. Рынок земли 435.5 KB
  Термин «земля» охватывает все блага, данные природой в определенном объеме, регулировать который человек не властен, будь то сама земля, водные ресурсы или полезные ископаемые. Для фермера участок земли служит средством для возделывания сельскохозяйственных культур, для горожанина – территориальной площадкой для размещения жилых и производственных зданий
3542. Открытие и развитие нового производства на примере ООО Ариран 706 KB
  Хотя планирование успешно применяется в корпорациях и компаниях развитых стран мира уже столетия и считается необходимой предпосылкой эффективного и стабильного бизнеса, в России при переходе к рыночным отношениям оно оказалось практи...
3543. Тенденции развития печатной техники 342.84 KB
  Тенденции современного рынка печатной продукции оказали большое влияние и на развитие полиграфической техники. Всем понятно, что типографии будут приобретать то оборудование, которое позволит им успешнее работать на рынке в сложившейся ситу...
3544. СМИ как инструмент PR в условиях региона (на примере города Брянска) 1.48 MB
  Актуальность исследования. Одной из основных форм PR-деятельности является подготовка текстовых материалов для целевой общественности и для СМИ. PR-материалы любой организации создаются с целью продвижения информации для достижения понимани...