Поделиться через


Вызов события

Чтобы класс мог породить событие, необходимо подготовить три следующих элемента:

  • Класс, предоставляющий данные для события.

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

  • Класс, порождающий событие.

Определение класса, предоставляющего данные для события

По действующему в .NET Framework соглашению при порождении события данные события передаются обработчикам событий. Данные события предоставляются классом System.EventArgs или его наследником.

Часто у события нет пользовательских данных. Факт порождения события уже предоставляет все необходимые обработчикам событий данные. В данном случае событие может передавать своим обработчикам объект EventArgs. У класса EventArgs имеется только один член, Empty, не унаследованный от System.Object. Он может использоваться для создания нового экземпляра EventArgs.

Если в событии имеются пользовательские данные, то оно может передавать обработчикам событий экземпляр класса, наследующего от EventArgs. В зависимости от конкретных данных, передаваемых событием обработчикам, в .NET Framework может иметься возможность использования существующего класса данных события. Например, если обработчик событий позволяет отменить действие, связанное с событием, можно использовать класс CancelEventArgs.

Если требуется предоставить обработчикам дополнительные пользовательские данные, но подходящего класса не существует, можно определить собственный класс данных события. Такой класс должен наследовать от System.EventArgs. По соглашению класс должен иметь имя Имя_событияEventArgs. В приведенном ниже примере кода демонстрируется подобный пользовательский класс данных события. Здесь определяется класс AlarmEventArgs, предоставляющий обработчикам событий два элемента данных: доступное только для чтения свойство Time, указывающее на время срабатывания будильника, и свойство Snooze, указывающее, должен ли будильник сработать повторно через заданное время, или же последующие срабатывания следует отменить.

Public Class AlarmEventArgs : Inherits EventArgs
   Private alarmTime As Date
   Private snoozeOn As Boolean = True

   Public Sub New(time As Date)
      Me.alarmTime = time
   End Sub

   Public ReadOnly Property Time As Date
      Get
         Return Me.alarmTime
      End Get
   End Property

   Public Property Snooze As Boolean
      Get
         Return Me.snoozeOn
      End Get
      Set
         Me.snoozeOn = value
      End Set   
   End Property   
End Class
public class AlarmEventArgs : EventArgs
{
   private DateTime alarmTime;
   private bool snoozeOn = true;

   public AlarmEventArgs(DateTime time)
   {
      this.alarmTime = time;
   }

   public DateTime Time
   {
      get { return this.alarmTime; }
   }

   public bool Snooze
   {
      get { return this.snoozeOn; }
      set { this.snoozeOn = value; }
   }   
}
public ref class AlarmEventArgs : public EventArgs
{
private: 
   System::DateTime^ alarmTime;
   bool snoozeOn;

public:
   AlarmEventArgs(System::DateTime^ time) 
   {
      this->alarmTime = time;
      this->snoozeOn = true;
   }

   property DateTime^ Time 
   {
      System::DateTime^ get()
      { return this->alarmTime; }
   }

   property bool Snooze
   {
      bool get()
      { return this->snoozeOn; }
      void set(bool snooze)
      { this->snoozeOn = snooze; }
   }
};

Определение делегата для события

Для определения сигнатуры события используется делегат события. Обычно конкретный делегат события соответствует конкретному классу данных события. По соглашению события в .NET Framework имеют сигнатуру Имя_события(sender, e), где sender — это объект Object, задающий ссылку на класс или структуру, породившую событие, а e — это объект EventArgs или класса, производного от EventArgs, предоставляющий данные события. При этом определение делегата обычно принимает вид Имя_событияHandler(sender, e).

При использовании класса данных события, уже определенного в библиотеке классов платформы .NET Framework или библиотеке стороннего разработчика, существует вероятность, что в той же библиотеке уже был определен и соответствующий делегат события. Например, делегат EventHandler может использоваться вместе с классом EventArgs. Аналогично, делегат CancelEventHandler может использоваться с классом CancelEventArgs.

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

В следующем примере определяется делегат события AlarmEventHandler.

Public Delegate Sub AlarmEventHandler(sender As Object, e As AlarmEventArgs)
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
public delegate void AlarmEventHandler(System::Object^ sender, AlarmEventArgs^ e);

Определение класса, порождающего событие

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

Член-событие определяется в классе с помощью ключевого слова event в C# или оператора Event в Visual Basic. Когда компилятор обнаруживает в классе объявление события, он создает закрытый элемент, например:

private EventNameHandler eh = null;

Компилятор также создает два открытых метода: add_EventName и remove_EventName. Эти методы являются обработчиками событий, которые позволяют объединять или удалять делегаты из делегата события eh. Эти подробности скрыты от программиста.

ПримечаниеПримечание

В языках, отличных от C# и Visual Basic 2005, компилятор может не создавать автоматически код, соответствующий элементу события и, возможно, потребуется вручную явным образом определить обработчики событий и поле закрытого делегата.

В следующем примере объявляется событие AlarmEvent. Оно взято из класса Alarm, полный исходный код которого приведен ниже. Обратите внимание, что его сигнатура соответствует делегату AlarmEventHandler.

Event AlarmEvent As AlarmEventHandler
public event AlarmEventHandler AlarmEvent;
public:
   event AlarmEventHandler^ AlarmEvent; 

После определения реализации события, необходимо определить, когда следует инициировать событие. Событие порождается путем вызова защищенного метода OnИмя_события в классе, в котором оно определено, или в производном от него классе. Затем метод OnИмя_событияпорождает событие.

ПримечаниеПримечание

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

В следующем примере определяется метод OnAlarmEvent, отвечающий за порождение события AlarmEvent.

Protected Sub OnAlarmEvent(e As AlarmEventArgs)
   RaiseEvent AlarmEvent(Me, e)
End Sub  
protected void OnAlarmEvent(AlarmEventArgs e)
{
   AlarmEvent(this, e);
}  
protected:
   void OnAlarmEvent(AlarmEventArgs^ e)
   {
      AlarmEvent(this, e);
   }

В следующем примере определяется метод Set, содержащий логику порождения события при помощи вызова метода OnAlarmEvent. Если часы и минуты на часах будильника совпадают с текущим временем, метод Set создает объект AlarmEventArgs и указывает в нем время срабатывания будильника. После выполнения обработчиков событий проверяется значение свойства Snooze. Если свойство Snooze равно false, то повторные срабатывания будильника не требуются, поэтому метод Set может завершить свою работу. Если свойство Snooze равно true, то время срабатывания будильника увеличивается на значение, заданное свойством Interval.

Public Sub [Set]()
   Do
      System.Threading.Thread.Sleep(2000)
      Dim currentTime As DateTime = Date.Now
      ' Test whether it is time for the alarm to go off.
      If currentTime.Hour = alarmTime.Hour And _
         currentTime.Minute = AlarmTime.Minute Then
         Dim args As New AlarmEventArgs(currentTime)
         OnAlarmEvent(args)
         If args.Snooze = False Then 
            Exit Sub
         Else
            Me.alarmTime = Me.alarmTime.AddMinutes(Me.interval)
         End If      
      End If          
   Loop
End Sub 
public void Set()
{
   while (true) {
      System.Threading.Thread.Sleep(2000);
      DateTime currentTime = DateTime.Now;
      // Test whether it is time for the alarm to go off.
      if (currentTime.Hour == alarmTime.Hour && 
          currentTime.Minute == alarmTime.Minute)
      {    
         AlarmEventArgs args = new AlarmEventArgs(currentTime);
         OnAlarmEvent(args);
         if (! args.Snooze) 
            return;
         else
            this.alarmTime = this.alarmTime.AddMinutes(this.interval);
      }
   }
} 
void Set()
{
   do {
      Thread::Sleep(2000);
      System::DateTime^ currentTime = DateTime::Now;
      // Test whether it's time for the alarm to go off.
      if (currentTime->Hour == alarmTime->Hour && currentTime->Minute == alarmTime->Minute)
      {
         AlarmEventArgs^ args = gcnew AlarmEventArgs(currentTime);
         OnAlarmEvent(args);
         if (args->Snooze == false)
            return;
         else
            this->alarmTime = this->alarmTime->AddMinutes(this->interval);
      }
   } while (true);
}

В следующем примере приведен полный исходный код класса Alarm.

Public Class Alarm
   Private alarmTime As Date
   Private interval As Integer = 10

   Event AlarmEvent As AlarmEventHandler

   Public Sub New(time As Date)
      Me.New(time, 10)
   End Sub

   Public Sub New(time As Date, interval As Integer)
      Me.alarmTime = time
      Me.interval = interval
   End Sub

   Public Sub [Set]()
      Do
         System.Threading.Thread.Sleep(2000)
         Dim currentTime As DateTime = Date.Now
         ' Test whether it is time for the alarm to go off.
         If currentTime.Hour = alarmTime.Hour And _
            currentTime.Minute = AlarmTime.Minute Then
            Dim args As New AlarmEventArgs(currentTime)
            OnAlarmEvent(args)
            If args.Snooze = False Then 
               Exit Sub
            Else
               Me.alarmTime = Me.alarmTime.AddMinutes(Me.interval)
            End If      
         End If          
      Loop
   End Sub 

   Protected Sub OnAlarmEvent(e As AlarmEventArgs)
      RaiseEvent AlarmEvent(Me, e)
   End Sub  
End Class
public class Alarm
{
   private DateTime alarmTime;
   private int interval = 10;

   public event AlarmEventHandler AlarmEvent;

   public Alarm(DateTime time) : this(time, 10)
   {
   }

   public Alarm(DateTime time, int interval)
   {
      this.alarmTime = time;
      this.interval = interval;
   }

   public void Set()
   {
      while (true) {
         System.Threading.Thread.Sleep(2000);
         DateTime currentTime = DateTime.Now;
         // Test whether it is time for the alarm to go off.
         if (currentTime.Hour == alarmTime.Hour && 
             currentTime.Minute == alarmTime.Minute)
         {    
            AlarmEventArgs args = new AlarmEventArgs(currentTime);
            OnAlarmEvent(args);
            if (! args.Snooze) 
               return;
            else
               this.alarmTime = this.alarmTime.AddMinutes(this.interval);
         }
      }
   } 

   protected void OnAlarmEvent(AlarmEventArgs e)
   {
      AlarmEvent(this, e);
   }  
}
public ref class Alarm 
{
private:
   System::DateTime^ alarmTime;
   int interval;

public:
   event AlarmEventHandler^ AlarmEvent; 
   Alarm(System::DateTime^ time) : alarmTime(time), interval(10) { };
   Alarm(System::DateTime^ time, int interval) : alarmTime(time), interval(interval) {};

   void Set()
   {
      do {
         Thread::Sleep(2000);
         System::DateTime^ currentTime = DateTime::Now;
         // Test whether it's time for the alarm to go off.
         if (currentTime->Hour == alarmTime->Hour && currentTime->Minute == alarmTime->Minute)
         {
            AlarmEventArgs^ args = gcnew AlarmEventArgs(currentTime);
            OnAlarmEvent(args);
            if (args->Snooze == false)
               return;
            else
               this->alarmTime = this->alarmTime->AddMinutes(this->interval);
         }
      } while (true);
   }

protected:
   void OnAlarmEvent(AlarmEventArgs^ e)
   {
      AlarmEvent(this, e);
   }
};

См. также

Задачи

Практическое руководство. Вызов и прием событий

Практическое руководство. Реализация событий в классе

Основные понятия

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

Другие ресурсы

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