Visual Basic .NET extends event handlers by supporting the dynamic association of events at runtime. This means you can write code in such a way as to create controls and tie them to other controls without knowing what those controls will be in advance. The result is that you can dynamically customize your application after it has been compiled and shipped.
You might recall that the AddressOf operator in VB6 supported passing the address of a procedure to a Windows API procedure. The passed address of the procedure is referred to as a callback function . Windows and other languages have supported callbacks for many years . VB6 allowed callback functions to support Windows API methods that needed them, but you could not use callbacks within your VB6 application.
Essentially, callback functions have now been extended to include internal use within a Visual Basic .NET application. The address of a procedure is at the heart of a delegate. Although a delegate is more than support for callback functions, this is the crux of what delegates are. (We'll return to delegates in a moment.)
Now that we have reviewed callbacks, we can talk about what callbacks have to do with events and event handlers. An event handler is a method whose address has been given to another function. When an event is raised, the method is invoked ”called back ”via the address of the procedure.
In a nutshell , events need callback functions, but VB6 provided limited support. Visual Basic .NET provides extended support for callback functions, which extends direct control over event handlers to you.
The AddHandler Statement
The AddHandler statement is used to associate an event handler with an event. The AddHandler statement has the following general syntax.
AddHandler object.event, AddressOf eventhandler
The first parameter is the object and event, and the second is the callback (also known as the event handler).
We could dynamically create a Button control and associate a handler with the Click event. The only requirement is that the event handler must have the same signature as the one expected by the event member.
Dim ADynamicButton As Button = New Button() ADynamicButton.Text = "AddHandler" AddHandler ADynamicButton.Click, AddressOf MyClick Controls.Add(ADynamicButton)
The preceding control dynamically creates a button, and the third statement associates a method named MyClick with the button's Click event. Here is a reasonable implementation of MyClick .
Private Sub MyClick(ByVal sender As Object, ByVal e As System.EventArgs) MsgBox("Click!") End Sub
What is not apparent from this code is that the AddressOf MyClick clause creates an instance of the EventHandler delegate. To demonstrate this you could rewrite the code to explicitly create the EventHandler delegate.
AddHandler ADynamicButton.Click, New EventHandler(AddressOf MyClick)
Another thing that is not apparent is that you can associate more than one handler with a single event (refer to the What Are Delegates? section). Removing a handler effectively turns off the associated event response. Think of radio stations and radios: Several radio stations broadcast, and each radio can tune to the different stations . Each radio station can have many listeners, and each can tune in or out.
The RemoveHandler Statement
You can prevent a specific event handler from being invoked by disassociating the handler with the event. This is accomplished by using the RemoveHandler method. The following code would remove the handler we added in the previous section.
RemoveHandler ADynamicButton.Click, AddressOf MyClick
The syntax of RemoveHandler is identical to that of AddHandler ; simply substitute RemoveHandler for AddHandler .
Assigning Event Handlers at Runtime
You know the mechanics of adding and removing event handlers at runtime. What you may not know is when or why you might actually use this capability.
The first obvious reason is that if you are going to add controls to your application at runtime, you will need event handlers to respond to those controls' events. Second, classes that are not controls but have events will need handlers to listen for those events. And there is at least one more thing you can do with the addresses of methods. (Keep in mind that event handlers are referred to as event handlers only because they are associated with events; more generally , event handlers are just the addresses of functions.) The third use of handlers is to dynamically change the behavior of a method by passing various callbacks to that method.
Using Callbacks to Implement Dynamic Sort Behavior
Suppose we elect to write a sort algorithm. The sort algorithm can be written to sort some elements of an array in ascending order, descending order, or in either order by passing a comparison callback to the sort method.
Let's keep the sort simple so we can focus on the dynamic callback behavior. Assume we want to pass an array of any kind of object to our sort method. This will work as long we implement a comparison function that knows how to compare the elements of the array. Passing a dynamic comparison function to our sort algorithm allows us to write a generic sort algorithm and use the comparison function to affect the sort order and resolve the type of the data. Listing 3.4 demonstrates such a solution.
Listing 3.4 Passing a Dynamic Comparison Function to a Sort Algorithm
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: 4: [ Windows Form Designer generated code ] 5: 6: Private Delegate Function Compare(ByVal lhs As Object, _ 7: ByVal rhs As Object) As Boolean 8: 9: Private Function Greater(ByVal lhs As Object, _ 10: ByVal rhs As Object) As Boolean 11: 12: Return CType(lhs, String) > CType(rhs, String) 13: 14: End Function 15: 16: Private Function Less(ByVal lhs As Object, _ 17: ByVal rhs As Object) As Boolean 18: 19: Return Not Greater(lhs, rhs) 20: 21: End Function 22: 23: Private Sub Sort(ByVal Items() As Object, ByVal CompareFunction As Compare) 24: 25: Dim I, J As Integer 26: For I = Items.GetLowerBound(0) To Items.GetUpperBound(0) - 1 27: For J = I + 1 To Items.GetUpperBound(0) 28: If (CompareFunction(Items(I), Items(J))) Then 29: 30: Dim Temp As Object = Items(I) 31: Items(I) = Items(J) 32: Items(J) = Temp 33: 34: End If 35: Next 36: Next 37: 38: End Sub 39: 40: Private Sub Button1_Click(ByVal sender As System.Object, _ 41: ByVal e As System.EventArgs) Handles Button1.Click 42: 43: Dim Items(ListBox1.Items.Count - 1) As Object 44: ListBox1.Items.CopyTo(Items, 0) 45: Sort(Items, AddressOf Less) 46: ListBox1.Items.Clear() 47: ListBox1.Items.AddRange(Items) 48: 49: End Sub 50: End Class
Lines 6 and 7 define the signature of our comparison function; it takes two objects and returns a Boolean. Notice that we are introducing the keyword Delegate . Don't worry about that right now; just keep in mind that the Delegate keyword is used to define delegates. For now simply think of the statement on lines 6 and 7 as a way to define the signature of a callback function.
Lines 9 through 14 and lines 16 through 21 define two functions that have the same signature as the delegate. (Compare them with lines 6 and 7.) Lines 23 through 38 define the Sort method. Sort takes an array of the most generic type, Object , and a Compare delegate. That is, the Sort method takes the address of a function that takes two object arguments and returns a Boolean.
Depending on which function we pass to Sort ” Greater or Less ”we will get a different result. If we pass Greater , we will get a sort in ascending order; if we pass Less , we will get a sort in descending order. If we create other functions that compare different data types, we can use the same Sort method to compare integers or any other array of data.
Using the IComparer Interface to Implement Dynamic Sort Behavior
Applying a dynamic comparator for a sort is a well-known solution. As a result, the Microsoft engineers have implemented a slightly different solution to dynamic sort behavior.
It would be reasonable for you to expect that ListBox controls can sort data, and you would be right. In addition, controls and classes that represent collections were implemented to support dynamic comparators. Microsoft used a slightly different approach for sort comparisons: an interface. (We covered interfaces in Chapter 2 and we'll discuss function pointers later in this chapter.) Listing 3.5 demonstrates how to implement the IComparer interface with the System.Array.Sort method to control the nature of the comparisons.
Listing 3.5 Implementing the IComparer Interface
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: 4: [ Windows Form Designer generated code ] 5: 6: Private Class Comparer 7: Implements IComparer 8: 9: Private Function Compare(ByVal lhs As String, _ 10: ByVal rhs As String) As Integer 11: 12: If (lhs > rhs) Then 13: Return 1 14: ElseIf (lhs < rhs) Then 15: Return -1 16: Else 17: Return 0 18: End If 19: 20: End Function 21: 22: Public Function Compare(ByVal x As Object, _ 23: ByVal y As Object) As Integer Implements IComparer.Compare 24: 25: Return Compare(CType(x, String), CType(y, String)) 26: End Function 27: 28: End Class 29: 30: Private Sub Button1_Click(ByVal sender As System.Object, _ 31: ByVal e As System.EventArgs) Handles Button1.Click 32: 33: Dim S(ListBox1.Items.Count - 1) As String 34: ListBox1.Items.CopyTo(S, 0) 35: Array.Sort(S, New Comparer()) 36: ListBox1.Items.Clear() 37: ListBox1.Items.AddRange(S) 38: 39: End Sub 40: End Class
Lines 6 through 28 define a private nested class that implements the IComparer interface. Since the comparer is used only by the form itself, it is an excellent candidate for nesting. (A nested class is defined inside another class. Try to do that with your father's VB.) The Comparer class implements the public Compare function (lines 22 through 26) to complete the contract represented by the IComparer interface. The overloaded private version of the Compare function (lines 9 through 20) enables us to separate the conversion to a string type from the actual comparisons. Passing arguments to an overloaded method is a strategy I employ to avoid temporary variables and to keep methods short.
The elements of the ListBox are copied to the array, and the Comparer and the array are passed to the Array.Sort method. The result is a sorted array. The Array.Sort method uses a very fast quick-sort algorithm; thus for most arrays of data, the Array.Sort method performs very well.
A final word here to be clear. This section and the preceding one were not added to demonstrate how to sort a ListBox . The ListBox was used to provide visual feedback. To sort a ListBox , set the ListBox.Sorted property to True . Use the IComparer or callback functions when you don't have the convenience of a ListBox and the ListBox.Sorted property. You certainly don't want to create ListBox control to sort nonvisual data.
Sorting is a common problem. If you are using a collection or a control that contains an array or a collection, examine that class's properties and methods. The class may already contain sorting capabilities, and you should probably use those before rolling your own sort.