Part One of this series builds a couple of examples where VB.NET events are coded without using Framework objects (like a Button or a TextBox). Visual Studio adds quite a bit of hidden code to implement events when you simply double-click a Framework control to add the event subroutine for it. You can do everything that Visual Studio does manually, but you have to really understand event coding to do it successfully. A handy reference to previous articles concerning events and delegates can also be found in Part One. Since some of this article is a continuation of that one, you might want to read that first.
To Start with Part One: Events Using Your Own Code in VB.NET.
The event coding option that I'll describe here ... with some cautions ... is the use of the Custom keyword when the event is declared. The event declaration from Part One (using the delegate MatchWonHandler) looked like this:
Public Event MatchWon As MatchWonHandler
If you add the Custom keyword, Visual Studio Intellisense automatically adds a code snippet with three new code blocks as soon as you press enter at the end of the statement. According to Microsoft, "Events declared as Custom must define custom AddHandler, RemoveHandler, and RaiseEvent accessors." So now you know what to call these code blocks: they're "accessors"!
Public Custom Event MatchWon As MatchWonHandler AddHandler(value As MatchWonHandler) End AddHandler RemoveHandler(value As MatchWonHandler) End RemoveHandler RaiseEvent(ww As Integer, tt As Integer) End RaiseEvent End Event
Notice that the delegate (MatchWonHandler in this case) and the parameters for it are already entered for you! The level of control that you get with the Custom keyword really isn't necessary for most programs and in the example from Part One, it's totally useless. In a way, this is a result of the really excellent implementation of events buried in Visual Studio. I'll add the Custom keyword to the Part One example anyway just to demonstrate how the events work.
Microsoft's documentation for the Custom keyword is in two categories: inadequate and none at all. But they do list two cases where it can be useful:
But these examples actually hide the detail about how it works because they don't explain how and why the VB code changes. This example does.
Because we have to code the custom AddHandler, RemoveHandler, and RaiseEvent accessors in our code (and the VB compiler doesn't add any hidden code for these same accessors), the WithEvents qualifier when the object that raises the event (in this case, the theMatch instance of TableTennisMatch) doesn't even have to be coded anymore.
Private WithEvents theMatch As TableTennisMatch
... becomes more simply ...
Dim theMatch As New TableTennisMatch
If you only add the Custom keyword, you will notice that the example from Part One immediately stops working. It doesn't crash; it just stops executing the event code. That's because the hidden code is not added when the keyword is there.
Just as in Part One, here's the complete code for the example. Explanations will follow.
Public Class TTMatchControl Dim theMatch As New TableTennisMatch Private Sub Form1_Load( sender As System.Object, e As System.EventArgs ) Handles MyBase.Load Dim PlayerA As String = "George" Dim PlayerB As String = "Arthur" theMatch.PlayerA = PlayerA theMatch.PlayerB = PlayerB Dim PStats = XElement.Load("..\..\PlayerStats.xml") Dim pStat = From p In PStats. Select p Where p..Value = PlayerA lblPlayerAWins.Text = pStat..Value pStat = From p In PStats. Select p Where p..Value = PlayerB lblPlayerBWins.Text = pStat..Value End Sub Private Sub btnAddPointA_Click( sender As System.Object, e As System.EventArgs ) Handles btnAddPointA.Click theMatch.UpdateScore(0) lblPlayerAScore.Text = CStr(theMatch.PlayerAScore) End Sub Private Sub btnAddPointB_Click( sender As System.Object, e As System.EventArgs ) Handles btnAddPointB.Click theMatch.UpdateScore(1) lblPlayerBScore.Text = CStr(theMatch.PlayerBScore) End Sub End Class Public Class TableTennisMatch Public Delegate Sub MatchWonHandler( ww As Integer, pA As String, pB As String) Property PlayerA As String Property PlayerB As String Property PlayerAScore As Integer Property PlayerBScore As Integer Public Custom Event MatchWon As MatchWonHandler AddHandler(value As MatchWonHandler) End AddHandler RemoveHandler(value As MatchWonHandler) End RemoveHandler RaiseEvent(ww As Integer, pA As String, pB As String) Record_WinLoss(ww, PlayerA, PlayerB) End RaiseEvent End Event Public Sub UpdateScore(ByVal pNum As Integer) If pNum = 0 Then PlayerAScore += 1 Else PlayerBScore += 1 End If If PlayerAScore > 20 Then If PlayerAScore > PlayerBScore + 1 Then RaiseEvent MatchWon(0, PlayerA, PlayerB) End If End If If PlayerBScore > 20 Then If PlayerBScore > PlayerAScore + 1 Then RaiseEvent MatchWon(1, PlayerA, PlayerB) End If End If End Sub Public Sub Record_WinLoss( ByVal ww As Integer, ByVal pA As String, ByVal pB As String) Dim PStats = XElement.Load("..\..\PlayerStats.xml") Dim pStatA = From p In PStats. Select p Where p..Value = pA Dim pStatB = From p In PStats. Select p Where p..Value = pB If ww = 0 Then pStatA..Value += 1 pStatB..Value += 1 TTMatchControl.lblPlayerWon.Text = pA & " Wins!" Else pStatB..Value += 1 pStatA..Value += 1 TTMatchControl.lblPlayerWon.Text = pB & " Wins!" End If PStats.Save("..\..\PlayerStats.xml") TTMatchControl.lblPlayerBWins.Text = pStatB..Value TTMatchControl.lblPlayerAWins.Text = pStatA..Value TTMatchControl.btnAddPointA.Enabled = False TTMatchControl.btnAddPointB.Enabled = False End Sub End Class
If you read Microsoft's pages about Custom event coding (linked above), you'll see that the code for the event source (in this case, "Public Custom Event MatchWon As MatchWonHandler"; in Microsoft's example, "Public Custom Event Click As EventHandler"), you'll notice that Microsoft's example is quite a bit more complex. For example, their RaiseEvent accessor block coding is:
CType(Events("ClickEvent"), EventHandler).Invoke(sender, e)
Mine is just a direct call to the subroutine that must be called when the event is raised:
Record_WinLoss(ww, PlayerA, PlayerB)
The reason is that Microsoft's example is intended to demonstrate how a custom event can be conditionally executed to allow a common memory resource to be used; mine is only intended to duplicate the function of the easier-to-code version in Part One to illustrate how using the Custom keyword can do the same thing. In my code the event handler, Record_WinLoss, is executed all the time. In Microsoft's example, it isn't, so they have to persist the event hander in a pointer variable: ClickEvent. Then the code that variable persists has to be executed with the Invoke method and the appropriate parameters. The parameters sender and e are the default parameters when you don't create your own when you declare the delegate. Recall that in Part One, I have unique parameters. In the final version:
Public Delegate Sub MatchWonHandler( ww As Integer, tt As Integer)
The key to understanding what the Custom keyword does is that the only automatic action will be to execute the appropriate accessor block whenever a handler is added, removed, or an event is raised. It can be very instructive to simply add a diagnostic debugging statement into each accessor - something like Console.Writeline("Accessor Block AddHandler") for the first one - to see when it is executed. This leads to all of the reasons why you might want to use one. If there is some code that you only want to run when a handler is removed - for example, display a warning to the user - then you can put that code in the RemoveHandler accessor block. Microsoft's example saves the event delegates in a list and checks the list.