Publish and Subscribe
In .NET, controls publish a set of events to which other classes can subscribe. When the publishing class raises an event, all the subscribed classes are notified.
|
With this event mechanism, the control says, "Here are things I can notify you about," and other classes might sign up, saying, "Yes, let me know when that happens." For example, a button might notify any number of interested observers when it is clicked. The button is called the publisher because the button publishes the Click event, and the other classes are the subscribers because they subscribe to the Click event.
4.1.1 Events and Delegates
Events are implemented with delegates. The publishing class defines a delegate that encapsulates a method that the subscribing classes implement. When the event is raised, the subscribing classes' methods (the event handlers) are invoked through the delegate.
|
When you instantiate a delegate, pass in the name of the method the delegate will encapsulate. Register the event using the += operator (in C#) or the Handles and WithEvents keywords in VB.NET. (VB.NET can alternatively use the AddHandler keyword.) You may register more than one delegate with an event; when the event is raised, each of the delegated methods will be notified.
For example, when declaring an event, the .NET documentation describes the event and delegate used in the Button control's Click event:
public delegate void EventHandler(object sender, EventArgs e); public event EventHandler Click;
EventHandler is defined to be a delegate for a method that returns void and takes two arguments: one of type Object and the other of type EventArgs. The Click event is implemented with the EventHandler delegate.
4.1.2 Event Arguments
By convention, event handlers in the .NET Framework are designated in C# to return void, are implemented as a sub in VB.NET, and take two parameters. The first parameter is the "source" of the event: the publishing object. The second parameter is an object derived from EventArgs.
EventArgs is the base class for all event data. Other than its constructor, the EventArgs class inherits all its methods only from Object, though it does add a public static field empty, which represents an event with no state (to allow for the efficient use of events with no state).
If an event has no interesting data to pass, then the passed event argument will be of type EventArgs, which has no public properties, being essentially a placeholder. However, if there is interesting data, such as the location of a mouseclick or which key was pressed, then the event argument will be of a type derived from EventArgs, and it will have properties for the data being passed.
The general prototype for an event handler is as follows:
private void Handler (object sender, EventArgs e)
Private Sub Handler (ByVal sender As Object, ByVal e As EventArgs)
|
Some events use the base class EventArgs, but EventArgs objects contain no useful additional information about the event. The controls that do require their event handlers to know additional information about the event will pass in an object of a type derived from EventArgs.
For example, the TreeView AfterCollapse event handler receives an argument of type TreeViewEventArgs, derived from EventArgs. TreeViewEventArgs has the properties Action and Node, each of which has values pertaining to the actual event. The specifics of the event argument for each control are detailed when that control is discussed later in this book.
4.1.3 Control Events
Every form and control used in Windows Forms derives from System.Windows.Forms.Control, so they inherit all of the more than 50 public events contained by the Control object. Some of the most commonly used Control events are listed in Table 4-1 through Table 4-3. For each (public) event, a protected method handles the event.
Many controls support other events, in addition to the inherited events. For example, the TreeView class exposes several events for handling node expansion and collapse. Control-specific events are detailed in the relevant sections.
Event |
Event argument |
Description |
---|---|---|
Click |
EventArgs |
Raised when a control is clicked by the mouse. |
ControlAdded |
ControlEventArgs |
Raised when a new control is added to Control.ControlCollection. |
ControlRemoved |
ControlEventArgs |
Raised when a control is removed from Control.ControlCollection. |
DockChanged |
EventArgs |
Raised if the Dock property i.e., which edge of the parent container the control is docked tois changed, either by user interaction or program control. |
DoubleClick |
EventArgs |
Raised when a control is double-clicked. If a control has both a Click and DoubleClick event handler, the DoubleClick will be preempted by the Click event. |
Enter |
EventArgs |
Raised when a control receives focus. Suppressed for Form objects. For nested controls, cascades up and down the container hierarchy. |
Layout |
LayoutEventArgs |
Raised when any change occurs that affects the layout of the control (e.g., the control is resized or child controls are added or removed). |
Leave |
EventArgs |
Raised when focus leaves the control. |
Move |
EventArgs |
Raised when a control is moved. |
Paint |
PaintEventArgs |
Raised when a control is redrawn. |
ParentChanged |
EventArgs |
Raised when the parent container of a control changes. |
Resize |
EventArgs |
Raised when a control is resized. Generally preferable to use the Layout event. |
SizeChanged |
EventArgs |
Raised when the Size property is changed, either by user interaction or programmatic control. Generally preferable to use the Layout event. |
TextChanged |
EventArgs |
Raised when the Text property changes, either by user interaction or programmatic control. |
Validating |
CancelEventArgs |
Raised when a control is validating. If CancelEventArgs Cancel property set true, then all subsequent focus events are suppressed. Suppressed if Control.CausesValidation property set false. |
Validated |
EventArgs |
Raised when a control completes validation. Suppressed if the CancelEventArgs.Cancel property passed to the Validating event is set true. Suppressed if Control.CausesValidation property set false. |
|
The events listed in Table 4-2 implement drag-and-drop.
Event |
Event argument |
Description |
---|---|---|
DragDrop |
DragEventArgs |
Raised when a drag-and-drop operation is completed. |
DragEnter |
DragEventArgs |
Raised when an object is dragged onto the control. At the time this event is raised, the drag operation is still in progress (i.e., the user hasn't yet let the mouse button go up). |
DragLeave |
DragEventArgs |
Raised when an object is dragged off of the control. |
DragOver |
DragEventArgs |
Raised when an object is dragged over the control. |
GiveFeedback |
GiveFeedbackEventArgs |
Raised during a drag operation to allow modification to the mouse pointer. |
The events listed in Table 4-3 are raised when a mouse interacts with a control. Some of these low-level events, in addition to being raised, are synthesized into the higher-level Click and DoubleClick events.
Event |
Event argument |
Description |
---|---|---|
MouseEnter |
EventArgs |
Raised when mouse pointer enters control. |
MouseMove |
MouseEventArgs |
Raised when mouse pointer moved over control. |
MouseHover |
EventArgs |
Raised when mouse hovers over control. |
MouseDown |
MouseEventArgs |
Raised when mouse button pressed while mouse pointer is over control. |
MouseWheel |
MouseEventArgs |
Raised when mouse wheel moved while control has focus. |
MouseUp |
MouseEventArgs |
Raised when mouse button released while mouse pointer is over control. |
MouseLeave |
EventArgs |
Raised when mouse pointer leaves control. |
4.1.4 Implementing an Event
Events were demonstrated back in Chapter 2. There, a Button was added to a form and the Click event for the button was handled. Handling the event was demonstrated both in a text editor and in Visual Studio .NET. Those examples also showed that the syntax and mechanics of handling events is somewhat different in C# and VB.NET, although the underlying fundamentals are the same.
The code samples shown in Example 4-1 (in C#) and Example 4-3 (in VB.NET) are duplicates of those shown in Example 2-5 and Example 2-6.
4.1.4.1 In C#
Example 4-1, reproduced here from Example 2-5, demonstrates the basic principles of implementing an event handler in C# by using a text editor.
Example 4-1. Hello World Windows application with button control in C# (HelloWorld-win-button.cs)
using System; using System.Drawing; using System.Windows.Forms; namespace ProgrammingWinApps { public class HelloWorld : System.Windows.Forms.Form { private Button btn; public HelloWorld( ) { Text = "Hello World"; btn = new Button( ); btn.Location = new Point(50,50); btn.Text = "Goodbye"; btn.Click += new System.EventHandler(btn_Click); Controls.Add(btn); } static void Main( ) { Application.Run(new HelloWorld( )); } private void btn_Click(object sender, EventArgs e) { Application.Exit( ); } } }
Handling an event in C# involves two steps:
Implement the event handler method
The event handler method, highlighted in Example 4-1, is called btn_Click. It has the required signature (two parameters: sender of type object and e of type EventArgs), and, as required, it returns void.
The code in the body of the event handler method performs whatever programming task is required to respond to the event. In this example, the event handler closes the application with the static method Application.Exit( ).
Hook up the event handler method to the event
This is done by instantiating an EventHandler delegate, which encapsulates the btn_Click method, then using the += operator to add that delegate to the button's Click event. This is done in Example 4-1 with the following line of code:
btn.Click += new System.EventHandler(btn_Click);
As easy as this is, Visual Studio .NET makes it even simpler. The fundamentals in working with events in the IDE will be shown with a simple application.
Open Visual Studio .NET and create a new Visual C# Windows Application project. Name it csEvents. Put a Label and Button control on the form. Using the Properties window, set the control properties to the values listed in Table 4-4.
Control type |
Property |
Value |
---|---|---|
Form |
(Name) |
Form1 |
Text |
Events Demonstrator |
|
Size |
250,200 |
|
Label |
(Name) |
lblTitle |
Text |
Events Demonstrator |
|
Size |
150,25 |
|
Button |
(Name) |
btnTest |
Text |
Do It! |
After the controls are placed and the properties set, Visual Studio .NET should look similar to Figure 4-1.
Figure 4-1. csEvents layout in Visual Studio .NET
There are several different ways to implement an event in Visual Studio .NET with C#.
|
Double-click the control
Double-click the button control. Visual Studio.NET takes this as an indication that you want to implement the default event handler for the button (the click event). Visual Studio.NET creates and registers an event handler, and moves you to the code window with the cursor placed in the body of the event handler:
private void btnTest_Click(object sender, System.EventArgs e) { }
Visual Studio.NET has created a method declaration that follows the event handler prototype exactly. The method name defaults to the name of the control with an underscore character and the default event name concatenated on the end.
|
Enter a line of code to pop up a message box in response to the button click:
private void btnTest_Click(object sender, System.EventArgs e) { MessageBox.Show("Click event just handled.","Event Demo"); }
Running the form will produce the application shown in Figure 4-2. Clicking the button will pop up a message dialog with the words "Click event just handled," as shown in Figure 4-3.
Figure 4-2. Events Demonstrator application
Figure 4-3. Event Demonstrator MessageBox
When you developed Example 4-1 in a text editor, you saw that in addition to implementing the event handler method, you also had to add a delegate encapsulating that method to the event. In Example 4-1, it was done with the following line of code:
btn.Click += new System.EventHandler(btn_Click);
When using Visual Studio .NET, registering the event is done for you automatically. This can be seen by going to the code window for the form, finding and expanding the region of code labeled Windows Form Designer generated code, and looking for the following section of code inside the InitializeComponent method:
// // btnTest // this.btnTest.Location = new System.Drawing.Point(56, 128); this.btnTest.Name = "btnTest"; this.btnTest.TabIndex = 2; this.btnTest.Text = "Do It!"; this.btnTest.Click += new System.EventHandler(this.btnTest_Click);
The highlighted line of code adds the method to the EventHandler delegate.
Use the lightning bolt icon in the Properties window
Double-clicking on the control only allows you to handle the default event using the default event handler method name. You can also create event handlers for any of a control's events, and name them whatever you like. To do so in C#, highlight the control in the design window and view the Properties window for the control (select View
At the top of the Properties window is a row of buttons, shown in Figure 4-4. The first two buttons on the left sort the window's contents by category or alphabetically. The right-most button displays Property pages, if any. Of most interest here are the remaining two buttons.
Figure 4-4. Property Window button bar
The Properties button (
If there is an event handler already defined for an event, it will be listed in the column next to the event name. Clicking on that name and pressing the Enter key will take you to that event handler in the code window.
If there is no event handler listed next to an event, highlight the event and press the Enter key to create an event handler with the default name. The method skeleton will be created, the event will be registered, and you will be taken to that method in the code window, where you can enter the body of the method.
Alternatively, you can enter any method name you wish. Pressing Enter will use the name you entered to create a skeleton event handler method and automatically hook up that event handler method to the event.
Finally, when an event is highlighted in the Properties window, a drop-down arrow will appear in the column for the method names. Clicking on the drop-down will display all the methods in the code available to be event handlers. This can be used to assign the same method to many different events, either for the same control or for different controls.
To demonstrate this last point, go to the code window for csEvents. Create a generic event handler method by adding the code shown in Example 4-2 to the Form1 class.
Example 4-2. Generic event handler in C#
private void GenericEventHandler(object sender, EventArgs e) { MessageBox.Show("Generic event handler", "Event Demo"); }
Now go back to the form designer and highlight the button. If the Events are not visible in the Property window, click on the Events button. Slide down to the MouseEnter event and click the drop-down arrow. You will see all the available methods, as shown in Figure 4-5.
Figure 4-5. Event drop-down
Click on GenericEventHandler to hook that handler method to the event.
Now add the same event handler to the Click method of the Label control named lblTitle. Click on the lblTitle control, find the Click event in the list of events in the Property window, click the drop-down arrow, and select GenericEventHandler. Run the program.
You will see that every time the mouse moves over the button, a dialog box similar to that shown in Figure 4-3 appears with the message Generic Event Handler. In fact, it is not possible to click on the button with your mouse because the MouseEnter event occurs before you have the opportunity to click on the button. (You can however click the button by pressing the Enter key once the button has focus.)
Clicking on the label containing the title also brings up the Generic Event Handler message.
Visual Studio .NET does all the work of hooking the GenericEventHandler method to both the lblTitle Click event and the btnTest MouseOver event. You can see how this was done by examining the region of code labeled Windows Form Designer generated code. In the section of code initializing the lblTitle control, the following line hooks the GenericEventHandler method to the Click event:
this.lblTitle.Click += new System.EventHandler(this.GenericEventHandler);
Similarly, the GenericEventHandler method is hooked to the btnTest MouseEnter event with this line of code:
this.btnTest.MouseEnter += new System.EventHandler(this.GenericEventHandler);
|
In the unusual case that you want more than one handler for a single event, you can add as many event handler methods to an event as you wish simply by implementing the event handler methods, and then adding those events to the delegate with additional += statements. (Even in Visual Studio .NET, this requires inserting the lines of code yourself.)
Similarly, you can remove an event handler method by using the -= operator. For example, the following code snippet adds three methods to a delegate for handling the Click event, then removes one of the methods from the delegate. In this way, it is possible to add and remove event handler methods dynamically and thereby start and stop event handling for specific events anywhere in your program:
btn.Click += new System.EventHandler(GenericEventHandler); btn.Click += new System.EventHandler(SpecialEventHandler); btn.Click += new System.EventHandler(ClickEventHandler); btn.Click -= new System.EventHandler(GenericEventHandler);
4.1.4.2 In VB.NET
Example 4-3, reproduced from Example 2-6, demonstrates the basic principles of implementing an event handler in VB.NET, using a text editor.
Example 4-3. Hello World Windows application with button control in VB.NET (HelloWorld-win-button.vb)
imports System imports System.Drawing imports System.Windows.Forms namespace ProgrammingWinApps public class HelloWorld : inherits System.Windows.Forms.Form Private WithEvents btn as Button public sub New( ) Text = "Hello World" btn = new Button( ) btn.Location = new Point(50,50) btn.Text = "Goodbye" Controls.Add(btn) end sub public shared sub Main( ) Application.Run(new HelloWorld( )) end sub private sub btn_Click(ByVal sender as object, _ ByVal e as EventArgs) _ Handles btn.Click Application.Exit( ) end sub end class end namespace
As with the C# example shown in Example 4-1, there are two steps to handling an event in VB.NET. However the syntax used in VB.NET is somewhat different than that in C#.
Implement the event handler method
The event handler method, highlighted in Example 4-3, is called btn_Click. It has the required signature (two objects: sender of type object and e of type EventArgs). Since this method is a subroutine, denoted by the sub keyword, it does not return a value. Event handlers never return a value.
The Handles keyword specifies which event this method will handle. The identifier following the keyword indicates that this method will handle the Click event for the Button called btn.
The code in the body of the event handler method performs whatever programming chore is required. In this example, it closes the application with the static method Application.Exit( ).
Instantiate the control using the WithEvents keyword
Unlike in C#, there is no code here to explicitly add the method to the delegate. Instead, a Button is declared as a private member variable using the keyword WithEvents. This keyword tells the compiler that this object will raise events:
Private WithEvents btn as Button
The compiler automatically creates delegates for any events referred to by a Handles clause and adds the event handler methods to the appropriate delegate.
Implementing events in VB.NET is made even easier when using Visual Studio .NET. To demonstrate this, open Visual Studio .NET and create a new VB.NET Windows application project called vbEvents.
Put a Label control and a Button control on the form. Using the Properties window, set the control properties to the values listed in Table 4-4 (the same values used in the C# example).
You can use VB.NET in several different ways to implement events in Visual Studio .NET.
Double-click the control
Double-click the Button control. You will be brought immediately to the code-editing window for the control, ready to enter code for the default event. The following code skeleton for the event handler method will be in place, with the cursor properly placed for you to commence typing the body of the method:
Private Sub btnTest_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnTest.Click End Sub
|
This method declaration exactly follows the event handler method prototype for VB.NET. The method name defaults to the name of the control with an underscore character and the default event name concatenated on the end. You can, however, use any name you wish, since the actual relationship between the method and the event it handles is dictated by the Handles keyword.
|
Enter a line of code to pop up a message box so that the btnTest_Click method now looks like:
Private Sub btnTest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTest.Click MessageBox.Show("Click event just handled.","Event Demo") End Sub
Running the form will produce the same application previously developed in C#, shown in Figure 4-2. Clicking the button will pop up a message dialog with the words "Click event just handled," as shown in Figure 4-3.
In Example 4-3, which was developed in a text editor, you saw that in addition to implementing the event handler method, you must also instantiate the control using the WithEvents keyword. In Example 4-3, that was done with the following line of code:
Private WithEvents btn as Button
When using Visual Studio .NET, this step is done for you automatically. You can see it by going to the code window for the form, finding and expanding out the region of code labeled Windows Form Designer generated code, and looking for the following line of code:
Friend WithEvents btnTest As System.Windows.Forms.Button
In Example 4-3, the Private access modifier was used for the object, which restricts access to the object to the class of which it is a member, i.e., HelloWorld. Visual Studio .NET uses the Friend access modifier, which is somewhat more expansive, allowing access from any class within the project in which it is defined.
|
Use the drop-down lists at the top of the code window
Double-clicking on the control allows you to add code to the default event only by using the default event-handler method name. You can also create event handlers for any of a control's events. To do so in VB.NET, view the code window. At the top of the window are two drop-down lists, as shown in Figure 4-6. The drop-down on the left lists all the controls on the form, while the drop-down on the right lists all the possible events for each control (plus (Declarations), which moves the cursor to the top of the code window).
Figure 4-6. VB.NET Object and event drop-down lists
First select the control whose events you wish to handle from the left drop-down. Select the event to handle from the right drop-down. If the event handler already exists, the cursor will move to the subroutine. If the event handler subroutine does not exist, then it will be created, with the cursor located inside the code skeleton, ready to enter your code.
|
An event handler method can easily be renamed simply by editing the method declaration. There is no need to edit any other line of code, since the method name is not associated with the method until compile time.
It is not possible to directly assign the same event handler method to multiple events on a form using this syntax, as can be done in C#. This is because each event-handler method uses the Handles keyword to directly associate the method with a specific event. One way to accomplish this indirectly would be to call the same method from within multiple event handlers.
It is possible, however, to have multiple event-handler methods respond to the same event. This is accomplished by having the Handles keyword on each of the event-handler methods refer to the same event. In this situation, the order in which the multiple events will fire is not defined. If the order is important, then you need to use the dynamic event implementation and the AddHandler statement, described next.
|
Dynamic event implementation
In the VB.NET syntax described so far, there is no sign of delegates in the code. The compiler automatically creates the necessary delegates at compile time and adds the methods marked with the Handles keyword to the appropriate delegate for each event.
You can explicitly add the event handler methods to the delegate in VB.NET, as is done in C#, even though it is not the default way in which Visual Studio .NET handles events in VB.NET programs. This is done with the AddHandler statement. AddHandler does not provide any significant performance benefits over the Handles keyword, but does provide greater flexibility. AddHandler and RemoveHandler allow you to add, remove, and change the event handler associated with an event dynamically (in your code at runtime). You can also add multiple event handlers to a single event using AddHandler.
The listing in Example 4-4 shows the modifications to Example 4-3 necessary to use the AddHandler statement rather than the Handles keyword for implementing events. The new or modified lines are highlighted.
Example 4-4. Using AddHandler to implement events
imports System imports System.Drawing imports System.Windows.Forms namespace ProgrammingWinApps public class EventsDemo : inherits System.Windows.Forms.Form Private btn as Button public sub New( ) Text = "Events Demonstration - AddHandler" btn = new Button( ) btn.Location = new Point(50,50) btn.Text = "Test" Controls.Add(btn) AddHandler btn.Click, AddressOf btn_Click end sub public shared sub Main( ) Application.Run(new EventsDemo( )) end sub private sub btn_Click(ByVal sender as object, _ ByVal e as EventArgs) MessageBox.Show("btn_Click method","Events Demonstration") end sub end class end namespace
Three code changes are necessary to dynamically add event handlers in a VB.NET program.
- The line instantiating the control no longer includes the WithEvents keyword. (This is not mandatory, but the keyword is no longer needed.)
- The event handler method declaration no longer uses the Handles keyword.
- An AddHandler statement is included to add the event-handler method to the delegate that handles the event.
The AddHandler statement takes two comma-separated arguments. The first argument is the name of the event to be handled, using dot notation. It is a two-part name consisting of the name of the object and the name of the event. The second argument is the AddressOf keyword followed by the name of the method that handles the event.
The RemoveHandler statement uses the same syntax as the AddHandler statement. Together they let you start or stop event handling for any specific event anywhere in the program.
It is possible to use both techniques for event handling in the same program, even for the same event in the same control. Consider the program in Example 4-5, which uses both techniques to handle the Click event for the button.
Example 4-5. Using both AddHandler and Handles to handle events
imports System imports System.Drawing imports System.Windows.Forms namespace ProgrammingWinApps public class EventsDemo : inherits System.Windows.Forms.Form Private WithEvents btn as Button public sub New( ) Text = "Events Demonstration - AddHandler" btn = new Button( ) btn.Location = new Point(50,50) btn.Text = "Test" Controls.Add(btn) AddHandler btn.Click, AddressOf btn_Click end sub public shared sub Main( ) Application.Run(new EventsDemo( )) end sub private sub btn_Click(ByVal sender as object, _ ByVal e as EventArgs) MessageBox.Show("btn_Click method","Events Demonstration") end sub private sub btn_ClickHandles(ByVal sender as object, _ ByVal e as EventArgs) _ Handles btn.Click MessageBox.Show("btn_ClickHandles method","Events Demonstration") end sub end class end namespace
The WithEvents keyword was added back to the line instantiating the Button object. It has no effect on the AddHandler statement functionality. However, it does enable the btn_ClickHandles method, which utilizes the Handles keyword, to also handle the button Click event.
When the program is compiled and run, the btnClickHandles method, which the compiler adds to the delegate behind the scenes, is called first. It is followed by the btn_Click method, which is added to the delegate by the AddHandler statement.
The following two statements are equivalent:
btn.Click += new System.EventHandler(GenericEventHandler);
AddHandler btn.Click, AddressOf GenericEventHandler
Категории