Windows is an event-based , or messaging, operating system. This means that Windows applications communicate by sending messages to controls that have Windows handles, and controls that have Windows handles can forward messages to constituent controls.
A few years ago programmers had to handle raw messages. Later, in an evolutionary step, raw messages were converted to method invocations. The messages are commonly referred to as events , and the methods that respond are dubbed event handlers .
An event handler is quite literally a method designated to respond to message events. One of the most common tasks you will perform is to associate event handlers with control events and then write code for those handlers. This is very similar to how to we created event handlers in VB6. We'll begin here to find some familiar ground and then proceed by exploring what has been revised.
Using the Form Designer
The easiest way to generate an event handler is to double-click on a control. The Form Designer will generate the event handler designated as the default event. For example, if you double-click on a form, the Load event handler will be generated, and focus will be switched to the Code Designer view.
So far this is exactly like VB6. However, there are differences. The signature of the Load event handler looks different, and the event handler is wired to the control differently. Listing 3.1 shows a VB .NET form that contains just the Load event handler.
Listing 3.1 Code for a VB .NET Form with the Generated Load Event Handler
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: 4: #Region " Windows Form Designer generated code " 5: 6: Public Sub New() 7: MyBase.New() 8: 9: 'This call is required by the Windows Form Designer. 10: InitializeComponent() 11: 12: 'Add any initialization after the InitializeComponent() call 13: 14: End Sub 15: 16: 'Form overrides dispose to clean up the component list. 17: Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) 18: If disposing Then 19: If Not (components Is Nothing) Then 20: components.Dispose() 21: End If 22: End If 23: MyBase.Dispose(disposing) 24: End Sub 25: 26: 'Required by the Windows Form Designer 27: Private components As System.ComponentModel.IContainer 28: 29: 'NOTE: The following procedure is required by the Windows Form Designer 30: 'It can be modified using the Windows Form Designer. 31: 'Do not modify it using the code editor. 32: <System.Diagnostics.DebuggerStepThrough()> _ 33: Private Sub InitializeComponent() 34: ' 35: 'Form1 36: ' 37: Me.AutoScaleBaseSize = New System.Drawing.Size(6, 15) 38: Me.ClientSize = New System.Drawing.Size(292, 260) 39: Me.Name = "Form1" 40: Me.Text = "Form1" 41: 42: End Sub 43: 44: #End Region 45: 46: Private Sub Form1_Load(ByVal sender As System.Object, _ 47: ByVal e As System.EventArgs) Handles MyBase.Load 48: 49: End Sub 50: End Class
At first glance this seems like a lot of code. However, if you look at the code in a VB6 .frm file (see Listing 3.2), you'll see that the VB .NET code is not that much longer.
Listing 3.2 Code for a VB6 Form with the Generated Load Event Handler
1: VERSION 5.00 2: Begin VB.Form Form1 3: Caption = "Form1" 4: ClientHeight = 2400 5: ClientLeft = 48 6: ClientTop = 432 7: ClientWidth = 3744 8: LinkTopic = "Form1" 9: ScaleHeight = 2400 10: ScaleWidth = 3744 11: StartUpPosition = 3 'Windows Default 12: End 13: Attribute VB_Name = "Form1" 14: Attribute VB_GlobalNameSpace = False 15: Attribute VB_Creatable = False 16: Attribute VB_PredeclaredId = True 17: Attribute VB_Exposed = False 18: Option Explicit 19: 20: Private Sub Form_Load() 21: 22: End Sub
I digress. Back to the code in Listing 3.1, which explicitly defines a class by using the class header (line 1) and the End Class statement (line 50). A constructor appears in lines 6 through 14 and a destructor in lines 17 through 24, and lines 37 through 40 contain form initialization code. Note that all the code from lines 4 to 44 is managed by the Form Designer.
Notice the signature of the Load event handler in lines 46 and 47 of Listing 3.1. Load has two parameters and a Handles clause at the end of the procedure header. The Handles clause indicates that this method ” Handles MyBase.Load ”handles the base class's Load event. (Refer to the upcoming subsection on the Handles clause for more information on this keyword and to the subsection on EventHandler for more information on the arguments passed to the Load event.)
Using the Form Designer is the easiest way to generate event handlers. You already know how to click on controls and add code; we won't elaborate on this point any longer.
Using the Code Editor
The next step familiar to VB6 developers is to generate events in the Code Editor (or Code Designer). The Code Editor has a list of class names at the top left of the editor and a list of method names at the top right. Select a class and a method, and .NET will generate any event handler available for you.
It is important to keep in mind that inheritance is an integral part of VB .NET. If you look at the list of method names for the Form1 control (see Figure 3.1), you are likely to be disappointed ”the list is very short.
Figure 3.1. A list showing method names available for the selected class ( Form1 ).
There is a reason the list is short. We are looking at the events for Form1 . Form1 is derived from Form , in which all the events are defined. If you select Base Class Events in the list of class names under Form1 , you will quickly see that the list of method names has increased substantially.
Select Base Class Events from the class name list and the Click method from the method name list to generate a Click event handler. You will quickly see that understanding the impact inheritance has on event name filtering is all you need to know to generate events in the Code Editor.
The Handles Clause
Visual Basic .NET no longer hides the mechanism that wires events to event handlers when those events are generated by the Form Designer or the Code Editor. The mechanism for this is the Handles clause.
Because the Handles clause is a prominent part of the code, it should be apparent that you can write the code manually to handle events at design time. For instance, if we want the Form Load and Click handlers to respond in the same way, we can simply add a MyBase.Click event to the comma-delimited list of events a particular method handles. Listing 3.3 shows the revision.
Listing 3.3 A Single Event Handler for Handling Multiple Events
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load, MyBase.Click MsgBox("Called on Load and Click") End Sub
The Handles clause is now understood to mean that Form1_Load will handle both the Form.Load and Form.Click events. This will work as long as the signatures of the events are identical. That is, one method can respond for any or all methods whose events have identical signatures. (I have not tested the limits of the number of events a single method can respond to or how long the Handles clause can be, but based on the implementation, it should be a very large number of events.)
The EventHandler Class
The signature of the Load and Click events (and many basic events in .NET) is a method that takes an object and a System.EventArgs argument. (This is actually the signature of a delegate class named EventHandler . We'll come back to the subject of delegates in the What Are Delegates? section later in this chapter.)
For now let's explore the arguments of the signature of this basic event handler.
The sender Argument
You have read by now that classes in Visual Basic .NET are derived from the Object class. Similarly, sender is defined as an object. This means that literally any type can be passed to satisfy the sender argument. What is usually passed is the originating object.
The result is that you can use runtime type checking to determine the class of the object that invoked a particular event. Of course, you could write code that depends on the specific object to which you assigned the event handler, but this is less robust, and if you change the name of the object, the code will be wrong. Referring to Listing 3.3, we could revise the code to refer to the object we know is associated with the Form1_Load handler.
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load, MyBase.Click MsgBox(Me.Name & " called on Load and Click") End Sub
The revised code relies on the reference to self, Me . Specifically, the code relies on the method being associated with the form.
How can we implement the code to be less dependent on the specific type of the object? Or how can we write the code to respond differently based on the actual invoker of the event? The answer is that we have to write the code to query the type of the sender object.
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load, MyBase.Click If (TypeOf sender Is Form1) Then MsgBox(CType(sender, Form1).Name & " called on Load and Click") End If End Sub
The revised code is more robust. The If statement checks to see if the type of the sender object is an instance of Form1 . If it is, sender is dynamically cast (cast at runtime) to its actual type. Casting the sender object supports accessing the members of that type. (For instance, object does not have a name property. However, if sender is an instance of Form1 and we cast it to Form1 , we can access the Form.Name property.)
By using dynamic type checking we take specific action based on the real type of the object that raised the event.
The EventArgs Argument
The EventArgs argument is a stub. In some instances events will want to pass additional information. For example, a mouse event may want to pass the position of the mouse. Keyboard events may want to pass the state of the keyboard. Paint events may want to pass the device context.
Other kinds of events actually use the EventArgs class, or derived classes, to pass additional information to the event handler. The Paint event handler passes a PaintEventArgs object that has a Graphics property representing the device context, or canvas, of the invoking object. You can use this Graphics object to perform custom painting, as we did in Chapter 2.
Handling Multiple Events with One Handler
You now know that you can manually associate similarly signatured events to a single event handler. You can accomplish this task by adding the name of the control and the name of the event in a Handles clause.
You may wonder why you would do this. The answer is that you have more than one metaphor that performs the same operation. (A control is a metaphor for invoking an operation.) For example, think of a form that has a menu that closes the form as well as a button that closes the form. Both the menu and the button are metaphors for closing the form. Thus, in addition to performing the same operation, both metaphors should share the same code. The short fragment that follows demonstrates this technique.
Private Sub ButtonClose_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ButtonClose.Click, MenuItemExit.Click Close() End Sub
The event handler was generated by double-clicking the button. From the Handles clause it is clear that this event handler handles the ButtonClose.Click event. I manually typed MenuItemExit.Click to add code for the menu item. The implication is that the user can perform the same operation ”closing the form ”by clicking the button or selecting the menu item.
A reasonable person might argue that the savings are minimal. In this example, yes, but when coding we have the opportunity to build habits that work efficiently and effectively in all circumstances. By sharing events that perform identical operations, we converge the code to one function. By calling a well-named function, the code does not need a comment, and we have to change the code in only one place if revisions are needed. To reiterate, we get speed, extensibility, and reliability by building solid general habits and employing them dogmatically. The preceding fragment demonstrates the following habits:
Convergent, well-named code helps programmers code at a much faster pace and yields a high-quality result. The objective is to make this style of programming second nature by practicing good habits.
Implementing Multiple Respondents
You know that you can make one method respond to multiple events. You can also make multiple event handlers respond to a single event. When an event invokes more than one method, this is referred to as multicasting .
By implementing multiple event handlers it is possible to layer behavior. For example, a producer can implement a custom Paint event handler for a user control, and a consumer of that control can implement a custom event handler too without overwriting the event handler associated with the Paint event by the producer. (We will return to this subject and provide examples in an upcoming section, What Are Delegates?)