Вызов события
Чтобы класс мог породить событие, необходимо подготовить три следующих элемента:
Класс, предоставляющий данные для события.
Делегат события.
Класс, порождающий событие.
Определение класса, предоставляющего данные для события
По действующему в .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);
}
};
См. также
Задачи
Практическое руководство. Вызов и прием событий
Практическое руководство. Реализация событий в классе