1. Technology

Using the Custom Event Keyword

Write your own custom AddHandler, RemoveHandler, and RaiseEvent accessors

By

Hands typing on laptop computer, high angle view
PhotoAlto/Milena Boniek/Brand X Pictures/Getty Images
Updated July 01, 2014

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:

-> Conserving Memory
-> Avoid Blocking

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.

  1. About.com
  2. Technology
  3. Visual Basic

©2014 About.com. All rights reserved.