Using Delegates Across Project Boundaries
Events can be defined and used across project boundaries. An event defined in a class library can be handled in an application making reference to that class and having defined an event handler for the class library event. The net result is that classes don't have to be defined within your application to take advantage of the event mechanism.
Complete the numbered steps.
After completing steps 1 through 10, run the Windows application part of the solution and click the button. You should get a message with the string of text sent from the event in the RaisesEvent class library. Listing 9.11 contains the complete code listing for the Windows application (this listing demonstrates a Windows Form class responding to an event raised in a class library). (The class library is in Listing 9.12).
Listing 9.11 Handle an event across project boundaries
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: 4: [ Windows Form Designer Code ] 5: 6: WithEvents R As RaisesEvent.RaiseTest 7: 8: Private Sub Button1_Click(ByVal sender As System.Object, _ 9: ByVal e As System.EventArgs) Handles Button1.Click 10: 11: Dim Obj As New RaisesEvent.RaiseTest() 12: R = Obj 13: Obj.Test() 14: 15: End Sub 16: 17: Private Sub R_SendString(ByVal Str As String) Handles R.SendString 18: MsgBox(Str) 19: End Sub 20: 21: End Class
The WithEvents statement sets up the event-handling capability for the RaiseTest class. Button1_Click creates a RaiseTest object and calls the Test method. RaisesEvent.Test raises the SendString event, which is handled on lines 17 through 21 of Listing 9.11.
Listing 9.12 demonstrates declaring the event and raising it.
Listing 9.12 Declare and raise an event in a class. The event can be handled in the same or external project
1: Public Class RaiseTest 2: 3: Public Event SendString(ByVal Str As String) 4: 5: Public Sub Test() 6: RaiseEvent SendString("RaisesEvent.Test") 7: End Sub 8: 9: End Class
This is identical to the code you would write if RaiseTest and Form1 were defined within the same project. The implied difference is that delegates are working behind the scenes to support this event interaction.
When you define a statement such as the one on line 3 of Listing 9.12, you are implicitly declaring a Delegate. The WithEvents statement is creating the necessary connection piece that allows you to express the relationship between event and handler. This relationship could have been explicitly defined, as demonstrated in the revised code in Listings 9.13 and 9.14.
Listing 9.13 Revision of the code from Listing 9.12, defining the form using delegates explicitly
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: 4: [ Windows Form Designer generated code ] 5: 6: Private Sub Button1_Click(ByVal sender As System.Object, _ 7: ByVal e As System.EventArgs) Handles Button1.Click 8: 9: Dim Obj As New RaisesEvent.RaiseTest() 10: Obj.D = AddressOf SendString 11: Obj.Test() 12: 13: End Sub 14: 15: Private Sub SendString(ByVal Str As String) 16: MsgBox(Str) 17: End Sub 18: 19: End Class
Note the absence of the WithEvents statement. Line 10 takes care of assigning the AddressOf the handler SendString to the Delegate member RaisesEvent.D. Look at Listing 9.14 for the revised implementation of the event and its invocation.
Listing 9.14 Revised implementation of RaiseTest from Listing 9.12, using Delegates instead of RaiseEvent
1: Public Class RaiseTest 2: 3: Public Delegate Sub Sender(ByVal Str As String) 4: Public D As Sender 5: 6: Public Sub Test() 7: D("RaisesEvent.Test") 8: End Sub 9: 10: Public Sub HandleClick(ByVal sender As System.Object, _ 11: ByVal e As System.EventArgs) 12: MsgBox("RaisesEvent.Click") 13: End Sub 14: End Class
In Listing 9.14, the Delegate is explicitly defined on line 3. Line 4 declares a public attribute named D, representing an instance of the Delegate Sender. Line 7 calls all of the procedures in D's invocation list oblivious to whether there are any procedures or not. The revised version performs exactly like its predecessor in Listing 9.12, without the Event and RaiseEvent statements.
The reverse of the behavior supported by Listings 9.11 and 9.12 or 9.13 and 9.14 can be implemented also. That is, you could define an event in Form1 and the handler in the RaiseTest class (or any class). Try this operation by creating a RaiseEvent object in the Form1 class. In Form1_Load use AddHandler Button1. Click, AddressOf Obj.Handler to add a procedure named Handler, defined in RaiseTest, to Button1's Click invocation list. When Button1 is clicked, RaiseTest's Handler event handler will respond. The code is defined in InterProjectEvent.sln along with the code from Listings 9.11 through 9.14.
Using Delegates explicitly may take a little getting used to if you were accustomed to using the WithEvents, RaiseEvent, and Event statements. However, calling the event using the same notation as a procedure is a little cleaner implementation.