Module 8: Delegates and Events |
21 |
|
|
|
" Events
Topic Objective
To provide an overview of the topics in this section.
Lead-in
An event is a way for a class to notify clients of that class of a change in an object.
!Event Scenario
!Declaring an Event
!Connecting to an Event
!Raising an Event
!.NET Framework Guidelines
*****************************ILLEGAL FOR NON-TRAINER USE******************************
An event is a way for a class to notify clients of that class when some interesting thing happens to an object. The most familiar use for events is in graphical user interfaces. Typically the classes that represent controls in the graphical user interface have events that are notified when a user manipulates the control, as when a user clicks a button. However, the use of events is not limited to graphical user interfaces. Events also allow an object to signal state changes that may be useful to clients of that object. Events are an important building block for creating classes that can be reused in many different programs.
Delegates are particularly suited for implementing events. You can use the
.NET event mechanism to easily provide an object with type safe methods that clients can call to register and deregister delegates to event handler methods. When an event is raised, the event handler methods are called.
22 |
Module 8: Delegates and Events |
Event Scenario
Topic Objective
To illustrate the .NET event mechanism through the scenario of a mouse clicked event.
Lead-in
This scenario of a mouse object that notifies interested objects when a mouse clicked event occurs illustrates one use of delegates and events.
|
Mouse Object |
SoundMaker Object |
|
MouseClicked field |
MouseClicked method |
|
Invocation List: |
|
MouseClicked method |
multicast delegate object |
multicast delegate object |
stopButton object |
|
|
MouseClicked method |
add_MouseClicked method |
MouseClickedmethod |
|
remove_MouseClicked method
OnMouseClicked
method
*****************************ILLEGAL FOR NON-TRAINER USE******************************
To run the build slide, click through the lower-left button on the slide.
This scenario of a mouse object that notifies interested client objects when a mouse click occurs illustrates one use of the .NET event mechanism. Client objects add and remove multicast delegate objects to the mouse object’s invocation list. These delegates specify the event handler methods to be called when a mouse click occurs. The mouse object code could be written to call a predefined list of event handler methods. However, this approach does not offer the flexibility to dynamically connect, disconnect, and reconnect methods to be called when the mouse click event occurs. By using multicast delegate objects, you can provide this flexibility.
In the mouse clicked event scenario that is shown in the slide, a mouse object encapsulates code to monitor a hardware mouse:
1.The mouse object’s class uses the Microsoft Visual C#™ compiler event keyword to have the compiler automatically create a private field named MouseClicked that is used to store a reference to the invocation list of delegates, and two public methods named add_MouseClicked and remove_MouseClicked that are used to add and remove delegates to this list. You can call these methods in Visual C# by using the += and -= operators.
2.The mouse object’s class declares an OnMousedClicked method that is called when a mouse click is detected. The OnMouseClicked method raises the MouseClicked event by invoking the invocation list’s first delegate object.
3.A user issues a command that causes sound to be generated whenever the mouse is clicked. This command causes the mouse object’s add_MouseClicked method to be called with a delegate object that points to the soundMaker object’s MouseClicked method. This delegate is added to the mouse object’s MouseClicked invocation list.
4.The mouse is clicked and the mouse object’s OnMouseClicked method is called to raise the mouse clicked event.
Module 8: Delegates and Events |
23 |
|
|
|
5.The OnMouseClicked method invokes the MouseClicked invocation list’s delegate.
6.The delegate calls the SoundMaker object’s MouseClicked method and a sound is generated.
7.A user then starts an application that operates a piece of machinery. The application displays a Stop button on the screen that the user clicks to stop the operation of the machinery. The application then instantiates a stopButton object and calls the mouse object’s add_MouseClicked method, passing a delegate object that refers to the stopButton object’s MouseClicked method. This delegate is added to the mouse object’s MouseClicked invocation list. The application then starts the machinery.
8.The mouse is clicked and the mouse object’s OnMouseClicked method is called to raise the MouseClicked event.
9.The OnMouseClicked method invokes the MouseClicked invocation list’s first delegate object. The delegate calls the SoundMaker object’s MouseClicked method and a sound is generated.
10.The first delegate object invokes the second delegate object. The second delegate object calls the stopButton object’s MouseClicked method. This method checks the location of the mouse cursor and after determining that it is located over the Stop button, halts the machinery.
11.The user then issues a command to terminate the application. The application calls the mouse object’s remove_MouseClicked method, passing a delegate object that refers to the stopButton object’s MouseClicked method. This delegate is removed from the mouse object’s MouseClicked invocation list.
24 |
Module 8: Delegates and Events |
Topic Objective
To explain how to declare events.
Lead-in
To declare an event inside a class, you first must declare a delegate type for the event.
! Declare the Delegate Type for the Event ! Declare the Event
# Like the field of delegate type preceded by an event keyword
// MouseClicked delegate declared // MouseClicked delegate declared
public delegate void MouseClickedEventHandler(); public delegate void MouseClickedEventHandler();
public class Mouse public class Mouse
{{
// MouseClicked event declared // MouseClicked event declared
public static event MouseClickedEventHandler public static event MouseClickedEventHandler
MouseClicked;
//...
MouseClicked;
}}
//...
*****************************ILLEGAL FOR NON-TRAINER USE******************************
To declare an event inside a class, you first must declare a delegate type for the event. The delegate type defines the set of arguments that are passed to the method that handles the event.
Multiple events can share a delegate type, so you need only declare a delegate type for an event if no suitable delegate type has already been declared. For example, in the case of a MouseClicked event in which the method that handles the event takes no arguments and returns void, the delegate is declared as follows:
public delegate void MouseClickedEventHandler();
Next, the event itself is declared. You declare an event as you would declare a field of delegate type, except that the keyword event follows the modifiers and precedes the delegate type. Events usually are declared public, but any accessibility modifier is allowed. The following code declares a class Mouse with an event named MouseClicked:
public class Mouse
{
public static event MouseClickedEventHandler MouseClicked;
//...
}
Module 8: Delegates and Events |
25 |
|
|
|
When you declare an event, the compiler generates a private field that references the end of a delegate invocation list. In the preceding example, a private field named MouseClicked, which refers to delegates of type
MouseClickedEventHandler, is created.
The compiler also generates two public methods for clients to call to compose and remove their delegate objects. In the preceding example, these public methods would be named add_MouseClicked and remove_MouseClicked. You can call these methods in C# by using the += and -= operators.
Because client access to the delegate invocation list is restricted to these methods, the use of the event keyword not only makes the implementation of events easier, it also prevents clients from accessing or raising the delegates of other clients.
26 |
Module 8: Delegates and Events |
Topic Objective
To describe how to connect to and disconnect from an event.
Lead-in
From outside the class that declared it, an event looks like a field, but access to that field is restricted.
! Connect by Combining Delegates ! Disconnect by Removing Delegates
// Client’s method to handle the MouseClick event
// Client’s method to handle the MouseClick event
private void MouseClicked() { //...
private void MouseClicked() { //...
}}
//...
//...
// Client code to connect to MouseClicked event // Client code to connect to MouseClicked event Mouse.MouseClicked += new
Mouse.MouseClicked += new MouseClickedEventHandler(MouseClicked); MouseClickedEventHandler(MouseClicked);
// Client code to break connection to MouseClick event // Client code to break connection to MouseClick event Mouse.MouseClicked -= new
Mouse.MouseClicked -= new MouseClickedEventHandler(MouseClicked); MouseClickedEventHandler(MouseClicked);
*****************************ILLEGAL FOR NON-TRAINER USE******************************
From outside the class that declared it, an event looks like a field, but access to that field is restricted. You can only add a delegate to an event or remove a delegate from an event. In C#, you add and remove delegates by using the += and -= operators. To begin receiving event invocations, client code first creates a delegate of the event type that refers to the method that should be invoked from the event. Then, the client code uses the + and – operators to compose that delegate with any other delegates to which the event might be connected. When the client code is finished receiving event invocations, it removes its delegate from the event by using the -= operator.
The following example shows how to connect to and disconnect from a
MouseClicked event:
public class aClient
{
// Client's method to handle the MouseClick event private void MouseClicked() {
//...
}
//...
// code to connect to Mouse class' MouseClicked event Mouse.MouseClicked += new
MouseClickedEventHandler(MouseClicked);
// code to break the connection to MouseClicked event Mouse.MouseClicked -= new
MouseClickedEventHandler(MouseClicked);
//...
}
Module 8: Delegates and Events |
27 |
|
|
|
Raising an Event
Topic Objective
To show when and how to raise an event.
Lead-in
After a class has declared an event, it can treat that event like a field of the indicated delegate type.
!Check Whether Any Clients Have Connected to This Event
#If the event field is null, there are no clients
!Raise the Event by Invoking the Event’s Delegate
if (MouseClicked != null) if (MouseClicked != null)
MouseClicked();
MouseClicked();
*****************************ILLEGAL FOR NON-TRAINER USE******************************
After a class has declared an event, it can treat that event like a field of the indicated delegate type. The field will be null if no client has connected a delegate to the event, or it will refer to a delegate that should be called when the event is raised. Therefore, when an event is raised, you should first check for null and then call the event. You can only raise an event from within the class that declared the event.
if (MouseClicked != null) MouseClicked();
28 |
Module 8: Delegates and Events |
.NET Framework Guidelines
Topic Objective
To introduce .NET Framework guidelines for delegates and events.
Lead-in
You should follow .NET Framework guidelines if you intend to use your component in the .NET Framework.
!Name Events with a Verb and Use Pascal Casing
!Use “Raise” for Events, Instead of “Fire”
!Event Argument Classes Extend System.EventArgs
!Event Delegates Return Void and Have Two Arguments
!Use a Protected Virtual Method to Raise Each Event
public class SwitchFlippedEventArgs : EventArgs { //...
public class SwitchFlippedEventArgs : EventArgs { //...
}}
public delegate void SwitchFlippedEventHandler( public delegate void SwitchFlippedEventHandler(
object sender, SwitchFlippedEventArgs e); object sender, SwitchFlippedEventArgs e);
public event SwitchFlippedEventHandler SwitchFlipped; public event SwitchFlippedEventHandler SwitchFlipped;
*****************************ILLEGAL FOR NON-TRAINER USE******************************
For Your Information
The Pascal Casing convention capitalizes the first character of each word. For example: BackColor
The camelCasing convention capitalizes the first character of each word, except the first word. For example: backColor
The .NET Framework includes guidelines on the delegate types that should be used for events. If you are creating a component for use with the .NET Framework, you should follow these guidelines:
!Consider naming events with a verb and use Pascal Casing. The Pascal Casing convention capitalizes the first character of each word. For example:
SwitchFlipped
Prefix preand post-event names by using the present and past tense. Do not use the BeforeXxx\AfterXxx pattern. For example, a close event that could be canceled should have a Closing and Closed event. An event that indicates that a control has been added should be declared as follows:
public event ControlEventHandler ControlAdded;
!Use “raise” for events, rather than “fired.”
When referring to events in documentation, use “an event was raised,” instead of “an event was fired.”
!For the event’s delegate declaration, use a return type of void, two parameters, an object source parameter named sender, and an e parameter that encapsulates any additional information about the event. Delegates should return void and have two arguments: the object that raised the event and the event data object. Use names ending in EventHandler for the event’s delegate, as in the following example:
public delegate void SwitchFlippedEventHandler(
object sender, SwitchFlippedEventArgs e);
The sender parameter represents the object that raised the event and called the delegate. The sender parameter is always a parameter of type object, even if you can employ a more specific type.
The state associated with the event is encapsulated in an instance of an event class named e. Use an appropriate and specific event class for its type.
Module 8: Delegates and Events |
29 |
|
|
|
!When necessary, event classes extend System.EventArgs and end in EventArgs, as in the following example:
public class SwitchFlippedEventArgs : EventArgs {//...
}
For events that do not use any additional information, the .NET Framework has already defined an appropriate delegate type: EventHandler, whose argument is of the event base type EventArgs, as in the following example:
public delegate void EventHandler( object sender, EventArgs e);
!Create an invoking method for the event.
Because events can only be raised from within the class that declared them, derived classes cannot directly raise events that are declared within the base class. Often it is appropriate to let the derived class raise the event. You can do this by creating a protected invoking method for the event. By calling this invoking method, derived classes can raise the event.
protected virtual void OnSwitchFlipped (//...) { //... }
For even more flexibility, you can declare the invoking method as virtual, which allows the derived class to override it. This approach allows the derived class to intercept the events that the base class is raising, possibly doing its own processing of them.
For this reason, the .NET Framework guideline suggests that you create an invoking method to raise an event that is a protected (family) virtual method. However, if the class is sealed, the method should not be virtual because the class cannot be derived from. Name the method OnEventName, where EventName is the event being raised.
30 |
Module 8: Delegates and Events |
The switch and light delegate scenarios in the preceding topics help to put these guidelines in context. When a switch is flipped, the switch object raises a SwitchFlipped event for connected light objects. In this case, the additional SwitchFlipped event data indicates whether the switch is in the up or down position:
public enum SwitchPosition {Up, Down};
// class to encapsulate switch data
public class SwitchFlippedEventArgs : EventArgs
{
private SwitchPosition position;
public SwitchFlippedEventArgs(SwitchPosition position) { this.position = position;
}
public SwitchPosition Position { get { return position; }
}
}
//delegate declaration
public delegate void SwitchFlippedEventHandler(
object sender, SwitchFlippedEventArgs e);
public class Switch
{
//event declaration
public event SwitchFlippedEventHandler SwitchFlipped;
//ProcessSwitchFlippedUp – called when switch flipped up
//to create an event argument object and raise the event public void ProcessSwitchFlippedUp() {
SwitchFlippedEventArgs e =
new SwitchFlippedEventArgs(SwitchPosition.Up); OnSwitchFlipped(e);
}
//ProcessSwitchFlippedDown – when switch flipped down public void ProcessSwitchFlippedDown() {
SwitchFlippedEventArgs e =
new SwitchFlippedEventArgs(SwitchPosition.Down); OnSwitchFlipped(e);
}
protected virtual void OnSwitchFlipped ( SwitchFlippedEventArgs e) {
if (SwitchFlipped != null) {
// call the delegate if non-null SwitchFlipped(this, e);
}
}
//...
}