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.
A demo named InterProjectEvent is on this book's companion web site, demonstrating the steps that are outlined next. Complete the numbered steps (or open the project from the CD) to follow along.
Create a new blank Windows application project.
Using the Solution Explorer, add a new Class Library project to the Windows application solution started in step 1.
Name the class library file RaisesEvent.vb and the class RaiseTest.
Declare an Event attribute by typing Public Event SendString( ByVal Str As String ).
Add a method test that raises the event with the statement RaiseEvent SendString("RaisesEvent").
In the Form Designer of the Windows application, add a button control. Double-click on the button control to generate the Click event handler for the button.
Outside the Button1_Click event handler, declare the statement WithEvents R As RaisesEvent.RaiseTest, where RaisesEvent is the class library and RaiseTest is the name of the class.
Within the Button1_Click event handler, create an instance of RaiseTest and assign it to R, the name in the WithEvents statement in step 7, and call the Test method with Obj.Test().
Select the name R from the objects list and R_SendString from the procedure list to generate the event handler in the form.
Add the statement MsgBox(Str) to the R_SendString event handler.
After completing steps 1 through 10, run the Windows application part of the solution and click on the button. You should get a message with the string of text sent from the event in the RaisesEvent class library. Listing 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 12.)
Listing 11Handle 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 11.
Listing 12 demonstrates declaring the event and raising it.
Listing 12Declare and Raise an Event in a Class. The Event Can Be Handled in the Same Project or an 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 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 13 and 14.
Listing 13Revision of the Code from Listing 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 14 for the revised implementation of the event and its invocation.
Listing 14Revised Implementation of RaiseTest from Listing 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 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 12, without the Event and RaiseEvent statements.
The reverse of the behavior supported by Listings 1112 or 1314 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 1114.
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.