Declaring and Raising Events
Events are supported for classes, modules, and structures in Visual Basic .NET. You can also define shared events (refer to Chapter 11 for more on Shared methods ).
The event model is designed so that you can define entities in such a way as to ensure that interactions are sustained at the interface level. (Keep in mind that the term interface is defined as the public properties, methods, and events of an entity, an instance of a class, module, or structure.)
Consider the following generic scenario. A grid control paints data in cells . When each cell paints , it needs to get data for each cell from the data provider. If the grid were implemented in such a way as to know what the cell data provider was and iterated over each cell, asking the provider for cell data, that grid would only be useful with that kind of data provider. The following algorithm demonstrates this:
For All Grid Cells Grid.Cell(index).Text = Provider(index).Text Next
This can work in a polymorphic way. For example, if Provider were an interface and any control that wanted to work with a specific grid supported that interface, the generic algorithm works with anything that is a suitable provider. Such an implementation works with COM interfaces or object-oriented, inherited interfaces. For example, if Provider were any OLEDB provider, the grid would work with any OLEDB provider.
As an alternative, the algorithm could be supported with an event as follows :
For All Grid Cells RaiseEvent GridData(index, ByRef Data As String) Next
Now the grid is generic and will work with any code that responds to the GridData event. A common implementation is to combine a general to specific approach to implementing classes. That is, classes at a lower level support a generic interface, like using an event, and as a class becomes more specific, the event is used to implement behavior for a specific kind of interaction. Using our grid example, an original grid class might support providing cell data via an event, and a specific subclass of the grid can provide the data from a Recordset.
A good general rule of object-oriented programming is that all entity interactions should occur through a property, method, or event. This rule doesn't apply to aggregate relationships. For example, if a combo box owns a list, internal interactions can occur with the specific list it owns. On the other hand, if a combo box needed a list of items but didn't maintain a list internally, it might expose a public property that allowed a consumer to associate a list with the combo box using a public property.
A violation of the entity interaction rule is commonly seen in code of the following form:
Form1.StatusBar1.Text = "Some Text"
From the fragment it's obvious that we are outside Form1 because we wouldn't need the Form1 reference from within Form1, and it would be dangerous to use an object name internally. What if the object weren't Form1? The result would be the dreaded access violation. At most we might use Me.StatusBar.Text to be verbose, but internally all we would need is StatusBar.Text. Having established that we are referencing the Text property of the status bar externally, we are presented with several problems. If status is implemented in some alternative fashion, all the external code referring to the StatusBar1 control would break. If the implementation of StatusBar changed, so that the property was renamed to Caption (a plausible example, as demonstrated by Visual Basic .NET forms using a Text instead of Caption property), again, all the code referring to Form1.StatusBar would be broken. In fact, code that generally daisy-chains object.member references together is demonstrative of tightly coupled code that violates the entity interaction rule.
To support loosely coupled entity interactions, we might support the status updating behavior with a property StatusText. Consequently, the consumer could call Form1. StatusText = "Some Text" and the provider could implement the status in any desired manner without breaking dependent code.
Supporting entity interactions with an event is preferable to a producer calling a specific method of a consumer. Consider the paint behavior of any Windows form. It's impossible for Windows to know in advance what all the forms will be from here to eternity. The arrangement is such that when any forms are rearranged or hidden and revealed, Windows raises a Paint event. Any form that needs to respond to Paint events can tap into this raised event and know when some external factor has made it necessary for it to repaint itself. Each form isn't specifically aware of the other forms or applications when it responds to a Paint event. Neither does Windows know about all possible instances of a form. What Windows does know is that its responsibility to displayed forms is to notify them when they need to repaint.
Events are critical to programming in Windows because events allow us to define specific interaction behaviors without indicating specific instances of objects.
The purpose of a property is to maintain state information. The purpose of a method is to define behavior. The purpose of an event is to support response behaviors. A producer raises an event and a consumer responds; the interchange between producer and consumer is complete.
When you define an entity, you will add events for those occurrences that you want to provide an opportunity to which consumers can respond. Events are often coupled with behaviors. For example, a form knows how to perform basic painting. A form responds to a paint event and raises the paint event for form consumers to provide additional responses. An example was demonstrated in Listing 8.7. MouseMove is an event that occurs when a person physically moves the mouse pointing device. Windows applications already know how to respond to MouseMove. In Listing 8.7, an additional response was defined: The cursor position of the mouse was displayed on the form. This position of the mouse was displayed in concert with an additional event, Paint. Many predefined responses exist; as mentioned, MouseMove and Paint are two such events. You can add events for any occurrence you think necessary when defining classes, structures, and modules.
Events are always subroutines. Events don't return data to producers . Hence the form of an event statement is always Event subname ( [args] ). The keyword Event is always required. The event name can be any meaningful name you'd like. And, because handlers are subroutines, you can define any number and type of arguments deemed necessary. Event arguments are completely optional just as subroutine arguments are.
Events also support access specifiers, allowing for public and nonpublic events. Prefix an event with the appropriate access specifier for example, Private to make the event an internal event or Public to allow consumers to respond to the eventas you would any other member.
Raising an event is similar to a function call. In essence what you are doing is invoking a procedure-calling mechanism when you raise an event, but you are invoking the procedure through the event name rather than the procedure name.
Events are always subroutines with zero or more arguments. To raise an event, write the keyword RaiseEvent followed by what looks like a subroutine call, passing the necessary arguments as defined by the event statement.
Consider a class with a Public Event OnTest(). Event handlers for OnTest events are subroutines that take no arguments. To raise an OnTest event, you would write RaiseEvent OnTest at the point in your code where you want the OnTest event to occur. Listing 8.10 demonstrates a class with the event OnTest.
The On prefix for event names was borrowed from Object Pascal. I will use it by convention for events throughout the rest of this book. The preposition On, as defined by www.dictionary.com, is "used to indicate occurrence at a given time," which clearly conveys what an event is. An event is an occurrence at a given time when a program is running.
Listing 8.10 A class containing one member, an event named OnTest.
1: Class TestClass 2: Public Event OnTest() 3: 4: Public Sub DoTest() 5: RaiseEvent OnTest() 6: End Sub 7: 8: End Class
TestClass defines a single event, OnTest. Another borrowed convention from Object Pascal is to raise an event in a procedure prefixed with Do. Hence, OnTest would be raised in DoTest. Wherever in your code an OnTest event needs to be raised, you would call DoTest.
You can elect to use Do and On or not; just be consistent with whichever conventions you adopt.
Suppose we wanted to pass the object of the invoking entity in an event, as the EventHandler delegate does. We could modify OnTest to require an argument defined as ByVal sender As Object. The revision applied to the Event statement would appear as Event OnTest(ByVal sender As Object). Revising Listing 8.10, line 5 to send the instance to the event consumer would be written as RaiseEvent OnTest(Me).
The reference to self, Me, is suitable as an Object reference because Me refers to the instance of the containing object and every object in Visual Basic .NET is subclassed from the Object class.
Implementing Class Event Handlers
A class event is an event defined as a member of a class. Listing 8.10 demonstrates a class event.
Often events are occurrences for which you want to support an external response. For this reason events are commonly defined as Public members. You can define class events as Protected, Private, Shared, and Friend Protected. These access specifiers have the same meaning when applied to events as they do when applied to other members .
Public events can be handled by event handlers in other entities. Protected and Protected Friend events are handled by friends and child classes, and private events are internal events. However, the purpose of an event is to define an opportunity for an as-yet undefined external entity to respond to an occurrence. Within the same entity, all the members are accessible and child classes have access to parent members, thus mitigating the general, widespread use of nonpublic events.
Implementing Shared Event Handlers
Property, method, and event members support the Shared specifier. Shared members are accessible without an instance. To declare a shared event, follow the access specifier with the keyword Shared and the event name and arguments. For more on implementing shared events, read Chapter 11, "Shared Members."
Implementing Module Event Handlers
You can define and raise events in modules. The Module construct was ported to Visual Basic .NET for reasons of familiarity . From Chapter 2, you know that the Module construct is fundamentally a class with all shared members.
Consequently we can guess that if shared members are supported, and shared events are supported, module events are likely to be supported. In fact they are. You don't need to use the Shared specifier for module events. Another difference between class events and module events is that module events aren't supported by the WithEvents statement and procedures with the Handles clause. You must use AddHandler to assign a delegatethat is, the address of a procedureto an event defined in a module. Consider the code in Listing 8.11, which defines a module event, and Listing 8.12, which defines the event handler.
Listing 8.11 Implementing a module event.
1: Module Module1 2: 3: Public Event OnEvent(ByVal Name As String) 4: 5: Sub DoEvent() 6: RaiseEvent OnEvent("Module1") 7: End Sub 8: 9: End Module
Listing 8.12 Implementing a module event handler.
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: 4: [...] 5: 6: Private Sub OnEvent(ByVal Name As String) 7: MsgBox(Name) 8: End Sub 9: 10: Private Sub button2_Click(ByVal sender As System.Object, _ 11: ByVal e As System.EventArgs) Handles button2.Click 12: 13: AddHandler Module1.OnEvent, AddressOf OnEvent 14: Module1.DoEvent() 15: 16: End Sub 17: 18: [...] 19: 20: End Class
Listing 8.11 defines the OnEvent event and the DoEvent method, which we are implementing by convention, used to raise the event. Note that the event declaration is the same whether the event is defined in a class or module. Listing 8.12 demonstrates the definition of the handler OnEvent bearing the same signature as the OnEvent event defined in Module1. (Another convention I often employ is to name my handlers the same as my events; this makes for an easier pairing between events and handlers. Because the event and the event handler are defined in separate entities, there is no name conflict in doing so.) The button2_Click event demonstrates defining the event handler OnEvent as the handler for Module1.OnEvent. The statement Module1.DoEvent on line 14 of Listing 8.12 simulates some fragment of code invoking the event handler.
As a general rule, it's the responsibility of the entity containing the event to raise the event. For example, a form raises the Paint event in response to the Invalidate method.
Implementing Structure Event Handlers
Structures support event members too. Structures, like modules, don't support the WithEvents statement and the Handles clause. You need to declare the structure and use AddHandler to associate an event handler with a structure. The motivation for defining a structure event is identical to defining an event for a module: you want to provide an external entity an opportunity to respond to an occurrence in the structure. Listings 8.13 and 8.14 demonstrate a structure with an event and an example of some handling code.
Listing 8.13 An event defined in a structure.
1: Structure Structure1 2: Public Event OnEvent(ByVal sender As Object) 3: 4: Sub DoEvent() 5: RaiseEvent OnEvent(Me) 6: End Sub 7: 8: End Structure
Listing 8.14 An event handler for a structure.
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: [...] 4: 5: Private Sub OnEvent(ByVal sender As Object) 6: MsgBox(sender.GetType().Name) 7: End Sub 8: 9: Private Sub button1_Click(ByVal sender As System.Object, _ 10: ByVal e As System.EventArgs) Handles button1.Click 11: 12: Dim S As Structure1 13: AddHandler S.OnEvent, AddressOf OnEvent 14: S.DoEvent() 15: 16: End Sub 17: 18: [...] 19: 20: End Class
Listing 8.13 defines an event, OnEvent, for a structure named Structure1. By our adopted convention, the event is raised in a Do subroutine. If the event were named OnStatusChange, by convention we would raise the event in a method named DoStatusChange. Structure1.OnEvent takes a single argument, which is satisfied by sending a reference to self. Listing 8.14 uses code outlining to hide extraneous form code not relevant to this discussion. Line 12 of Listing 8.14 declares an instance of Structure1. Line 13 associates the handler OnEvent with the Structure1.OnEvent event.
Unsupported Event Capabilities
Some general capabilities aren't supported for events. Events can't be overloaded or overridden in subclasses. However, if you implement a Do procedure that raises an event, you can override and overload that procedure, changing the way an event is invoked or even which event is called. Suppose you have an event OnEvent and you implement a subroutine DoEvent. Every time your code needs to raise OnEvent, you call DoEvent. Because DoEvent is a regular procedure, you can override or overload it.
Event signatures can contain ByRef or ByVal arguments but cannot use the Optional keyword and have optional arguments. The ParamArray specifier, which effectively allows you to include or not include arguments, is also precluded in the context of event statements.