
Events in the .NET Framework
An event is a message sent by an object to signal the occurrence of an action. The action could be caused by user interaction, such as a mouse click, or it could be triggered by some other program logic, such as an integer variable exceeding or falling below a particular threshold. The object that raises the event is called the event sender. The object that captures the event and responds to it is called the event receiver.
In event communication, the event sender class does not know which object or method will receive (handle) the events it raises. What is needed is an intermediary (or pointer-like mechanism) between the source and the receiver. The .NET Framework defines a special type (Delegate) that provides the functionality of a function pointer.
A delegate is a class that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and it can hold references only to methods that match its signature. A delegate is thus equivalent to a type-safe function pointer or a callback. While delegates have other uses, the discussion here focuses on the event handling functionality of delegates. A delegate declaration is sufficient to define a delegate class. The declaration supplies the signature of the delegate, and the common language runtime provides the implementation. The following example shows an event delegate declaration.
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
Public Delegate Sub AlarmEventHandler(sender As Object, e As AlarmEventArgs)
The syntax is similar to that of a method declaration; however, the delegate keyword informs the compiler that AlarmEventHandler is a delegate type. By convention, event delegates in the .NET Framework have two parameters, the source that raised the event, represented by the sender parameter, and the data for the event, represented by the e parameter.
An instance of the AlarmEventHandler delegate can bind to any event or method that matches its signature, such as the AlarmRang method of the WakeMeUp class shown in the following example.
public class WakeMeUp
{
// Alert has the same signature as AlarmEventHandler.
public void AlarmRang(object sender, AlarmEventArgs e)
{
// Method implementation.
}
// Other class members.
//
}
Public Class WakeMeUp
' AlarmRang has the same signature as AlarmEventHandler.
Public Sub AlarmRang(sender As Object, e As AlarmEventArgs)
' Method implementation.
End Sub
' Other class members.
'
End Class
Custom event delegates are needed only when an event generates event data. For events that do not generate event data, System..::.EventHandler, the delegate provided in the class library for the no-data event, is adequate. Its declaration is as follows.
delegate void EventHandler(object sender, EventArgs e);
Public Delegate Sub EventHandler(sender As Object, e As EventArgs)
Event delegates are multicast, which means that they can hold references to more than one event handling method. For details, see Delegate. Delegates allow for flexibility and fine-grain control in event handling. A delegate acts as an event dispatcher for the class that raises the event by maintaining a list of registered event handlers for the event.
Raising an Event
You can define events that are raised by your own classes, structures, and interfaces. The event can then be raised under a specified condition, such as the occurrence of a particular user action, a change in state, or a change in the value of a variable.
Note: |
|---|
This section lists the steps involved in raising an event and presents code fragments from a larger example that illustrate each step. Consuming an event is discussed in the
Consuming an Event section later in this topic, and the code to handle the events raised by the example code in this section is also presented there.
|
Defining a custom event requires that you provide three interrelated elements:
A class that holds event data. By convention, this class should be named EventNameEventArgs. This class must inherit from System..::.EventArgs. In some cases, it may be possible to use an existing event data class rather than define a custom one. For example, if your event does not use custom data, you can use System..::.EventArgs for your event data.
The following example defines a class named AlarmEventArgs to hold custom event data. It inherits from EventArgs and adds three additional properties: Rings, which indicates the number of times the alarm should ring when the event is raised; Snooze, which indicates whether the alarm event should be raised repeatedly at a regular interval after it is first raised; and Cancel, which indicates that the alarm event should stop being raised.
public class AlarmEventArgs
{
private int numberOfRings;
private bool snoozePressed;
private bool cancelled = false;
public AlarmEventArgs(bool snoozePressed, int numberOfRings)
{
this.snoozePressed = snoozePressed;
this.numberOfRings = numberOfRings;
}
public int Rings {
get { return this.numberOfRings; }
}
public bool Snooze {
get { return this.snoozePressed; }
}
public bool Cancel {
get { return this.cancelled; }
set { this.cancelled = value; }
}
}
Public Class AlarmEventArgs : Inherits EventArgs
Private numberOfRings As Integer
Private snoozePressed As Boolean
Private cancelled As Boolean = False
Public Sub New(snoozePressed As Boolean, numberOfRings As Integer)
Me.snoozePressed = snoozePressed
Me.numberOfRings = numberOfRings
End Sub
Public ReadOnly Property Rings As Integer
Get
Return Me.numberOfRings
End Get
End Property
Public ReadOnly Property Snooze As Boolean
Get
Return Me.snoozePressed
End Get
End Property
Public Property Cancel As Boolean
Get
Return Me.Cancelled
End Get
Set
Me.Cancelled = Value
End Set
End Property
End Class
A delegate for the event. By convention, event delegates are named EventNameEventHandler and have two parameters: an Object that indicates the source of the event, and an object derived from System..::.EventArgs that provides information about the event. Depending on the object that provides event data, it may be possible to use an existing event handler. For example, if your event does not use custom event data, you can use EventHandler for your delegate.
The following example defines an event delegate named AlarmEventArgs that takes two parameters: a reference to the object that raised the event, and an AlarmEventArgs object that contains custom event data.
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
Public Delegate Sub AlarmEventHandler(sender As Object, e As AlarmEventArgs)
A class, structure, or interface that provides the event declaration (EventName) and a method that raises the event. You define an event in your class using the event keyword.
You raise the event by calling the protected OnEventName method in the class that defines the event, or in a derived class. The OnEventName method raises the event by invoking the delegates and passing any event-specific data. The delegate methods for the event can perform actions for the event or process the event-specific data.
The following example defines an Alarm class. Its members include an AlarmEvent event and three properties, Rings, SnoozePressed, and Cancel, that correspond to the three properties of the AlarmEventArgs class. Members also include a protected OnAlarm method that raises the event and a Start method that is responsible for calling the OnAlarm method at regular intervals.
public class AlarmClock
{
public event AlarmEventHandler AlarmEvent;
protected int numberOfRings;
protected bool snooze;
public AlarmClock(int numberOfRings, bool snooze)
{
this.numberOfRings = numberOfRings;
this.snooze = snooze;
}
public int Rings {
get { return this.numberOfRings; }
}
public bool SnoozePressed {
get { return this.snooze; }
}
public void OnAlarm(AlarmEventArgs e)
{
AlarmEvent(this, e);
}
public void Start()
{
AlarmEventArgs e = new AlarmEventArgs(snooze, numberOfRings);
do {
System.Threading.Thread.Sleep(1500);
OnAlarm(e);
} while (! e.Cancel);
}
}
Note: |
|---|
The protected
OnEventName method also allows derived classes to override the event without attaching a delegate to it. A derived class must always call the OnEventName method of the base class to ensure that registered delegates receive the event. However, in a class that is sealed or NotInheritable, you can raise the event directly rather than raising it indirectly in the OnEventName method.
|
Consuming an Event
To consume an event in an application, you must provide an event handler (a method that handles the event) that executes program logic in response to the event and register the event handler with the event source. This process is referred to as event wiring. The exact technique used to wire events depends on the language. The following example shows the Visual Basic and C# code for an Example class that handles the AlarmEvent event and that defines an AlarmHandler method that is executed whenever the event is raised. Note that the event handler, by setting the AlarmEventArgs.Cancel property to true, stops the event from being raised repeatedly.
public class Example
{
private static System.Windows.Controls.TextBlock outputBlock;
private static AlarmClock alarm;
public static void Demo(System.Windows.Controls.TextBlock outputBlock)
{
Example.outputBlock = outputBlock;
alarm = new AlarmClock(3, true);
alarm.AlarmEvent += new AlarmEventHandler(Example.AlarmHandler);
outputBlock.Text += "Alarm started...\n";
alarm.Start();
}
private static void AlarmHandler(object sender, AlarmEventArgs e)
{
int totalAlarms = 0;
do {
for (int ring = 1; ring <= e.Rings; ring++)
outputBlock.Text += "Ring...";
outputBlock.Text += "\n";
if (totalAlarms == 3)
e.Cancel = true;
totalAlarms++;
} while (! e.Cancel);
}
}
Public Class AlarmClock
Public Event AlarmEvent As AlarmEventHandler
Protected numberOfRings As Integer
Protected snooze As Boolean
Public Sub New(numberOfRings As Integer, snooze As Boolean)
Me.numberOfRings = numberOfRings
Me.snooze = snooze
End Sub
Public ReadOnly Property Rings As Integer
Get
Return Me.numberOfRings
End Get
End Property
Public ReadOnly Property SnoozePressed As Boolean
Get
Return Me.snooze
End Get
End Property
Protected Overridable Sub OnAlarm(e As AlarmEventArgs)
RaiseEvent AlarmEvent(Me, e)
End Sub
Public Overridable Sub Start()
Dim e As New AlarmEventArgs(snooze, numberOfRings)
Do
System.Threading.Thread.Sleep(1500)
OnAlarm(e)
Loop While Not e.Cancel
End Sub
End Class
Public Module Example
Public WithEvents alarm As AlarmClock
Private outputBlock As System.Windows.Controls.TextBlock
Dim rings As Integer
Public Sub Demo(outputBlock As System.Windows.Controls.TextBlock)
Example.outputBlock = outputBlock
alarm = New AlarmClock(3, True)
outputBlock.Text += "Alarm started..." + vbCrLf
alarm.Start()
End Sub
Private Sub AlarmHandler(sender As Object, e As AlarmEventArgs) Handles alarm.AlarmEvent
Dim totalAlarms As Integer
Do
For ring As Integer = 1 To e.Rings
outputBlock.Text += "Ring..."
Next
outputBlock.Text += vbCrLf
If totalAlarms = 3 Then e.Cancel = True
totalAlarms += 1
Loop While Not e.Cancel
End Sub
End Module