When Visual Basic first emerged onto the programming scene ... (we're talking "age of dinosaurs" now) ... it was called event oriented programming and you still see that description used sometimes. That's a testament to how important events are to every part of programming as it's done today. I think it's as important as John Von Neuman's "stored program concept" back in 1945. (That's before the dinosaurs!)
About Visual Basic has a number of articles that cover different parts of event-oriented programming ...
Taken together, these cover most of the subject. But you have to put the pieces together yourself to get the whole picture. And they generally use Framework objects - like Button and Timer - to illustrate the use of events. This is the first of a two article series that builds the whole thing in one place and uses a class that is coded inside the example program so you can be sure that nothing is hidden. Part Two, Using the Custom Event Keyword, builds on this same theme by explaining how really custom events work.
The example program used in this article is inspired by a problem brought to an "in person" programming class I taught at a college recently. "Ralph" helped manage a table tennis tournament and wanted to upgrade some VB programs he used. So this program keeps score for one match in the tournament. The "event" that is coded happens when a player wins the match.
In addition to a complete, custom coded event, you might also be interested in this article as an example of ...
-> The use of an XML file to persist data
-> LINQ to XML to read and write the XML file
-> And, of course, the use of a custom class, which is simply another way of describing custom event code
Because a lot of people will just want to get to example code without wading through a lot of text, here's the complete program, followed by the XML file. Explanation will follow.
Public Class TTMatchControl Dim PlayerA As String = "George" Dim PlayerB As String = "Arthur" Private WithEvents theMatch As TableTennisMatch Private Sub Form1_Load( sender As System.Object, e As System.EventArgs ) Handles MyBase.Load theMatch = New TableTennisMatch lblPlayerA.Text = PlayerA lblPlayerB.Text = PlayerB Dim PStats = XElement.Load("..\..\PlayerStats.xml") Dim pStat = From p In PStats.<PStat> Select p Where p.<PID>.Value = PlayerA lblPlayerAWins.Text = pStat.<Won>.Value pStat = From p In PStats.<PStat> Select p Where p.<PID>.Value = PlayerB lblPlayerBWins.Text = pStat.<Won>.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 Public Sub Record_WinLoss( ByVal ww As Integer ) Handles theMatch.MatchWon Dim PStats = XElement.Load("..\..\PlayerStats.xml") Dim pStatA = From p In PStats.<PStat> Select p Where p.<PID>.Value = PlayerA Dim pStatB = From p In PStats.<PStat> Select p Where p.<PID>.Value = PlayerB If ww = 0 Then pStatA.<Won>.Value += 1 pStatB.<Lost>.Value += 1 lblPlayerWon.Text = PlayerA & " Wins!" Else pStatB.<Won>.Value += 1 pStatA.<Lost>.Value += 1 lblPlayerWon.Text = PlayerB & " Wins!" End If PStats.Save("..\..\PlayerStats.xml") lblPlayerBWins.Text = pStatB.<Won>.Value lblPlayerAWins.Text = pStatA.<Won>.Value btnAddPointA.Enabled = False btnAddPointB.Enabled = False End Sub End Class Public Class TableTennisMatch Event MatchWon(whoWon As Integer) Property PlayerAScore As Integer Property PlayerBScore As Integer 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) End If End If If PlayerBScore > 20 Then If PlayerBScore > PlayerAScore + 1 Then RaiseEvent MatchWon(1) End If End If End Sub End Class The XML file ... (PlayerStats.xml) <?xml version="1.0" encoding="utf-8"?> <PStats> <PStat> <PID>George</PID> <Won>17</Won> <Lost>8</Lost> </PStat> <PStat> <PID>Arthur</PID> <Won>9</Won> <Lost>20</Lost> </PStat> </PStats>
The example only has one form, shown below:
Click Here to display the illustration
The example starts with two players who will face off in a table tennis match. In a more practical application, these two players will also be in a data file (possibly an XML file to match the scores maintained in this file). In this example, string values for the two players ("George" and "Arthur") are simply hard coded.
The next statement ...
Private WithEvents theMatch As TableTennisMatch
... is a requirement for setting up a custom event. "theMatch" is coded later as an instance of the custom class, "TableTennisMatch". The keyword "WithEvents" tells the compiler to add code to the instance of a class that is created ...
theMatch = New TableTennisMatch
... later in the code. This is why WithEvents variables must be coded at the class level. This code added by the compiler will insert messages into the Windows "message pump" so another piece of code can be automatically called when the event occurs. The code that is called is identified by the Handles keyword ...
Public Sub Record_WinLoss( ByVal ww As Integer ) Handles theMatch.MatchWon
This is the custom event code that is the topic item of this article. The article Creating Your Own Event Code goes into more detail about this.
As an added example, the code also shows how easy it is to load, read, and update an XML file using the very SQL-like syntax of LINQ to XML introduced in Framework 3.5.
The event that signals when a match is won or lost is raised in the instantiated class TableTennisMatch. This class keeps track of the score in properties and whenever the score is changed, it checks to see if the game is over. If it is, the MatchWon() event is raised with a type Integer parameter that tells the code handling the event (Record_WinLoss) which player won the match. This code updates the XML file, announces the win, and disables the buttons.
The following illustration shows the event related code and how statements in the two classes tie together ... in this version.
Click Here to display the illustration
An alternative way to code this solution would be to explicitly code a delegate. I decided to put it completely outside the existing classes. That way, the scope would include those classes. I also coded the instantiation of the class, TableTennisMatch, as part of the TTMatchControl class rather than the form where it's used.
Public Delegate Sub MatchWonHandler(ww As Integer) Public Class TTMatchControl Dim theMatch As New TableTennisMatch
To wireup the event handler to the event, I used an AddHandler statement instead of the Handles clause on the event handler subroutine itself.
AddHandler theMatch.MatchWon, AddressOf Me.Record_WinLoss ... Public Sub Record_WinLoss( ByVal ww As Integer ) ' Note - No Handles clause
Then the Event itself refers to the delegate - MatchWon As MatchWonHandler - rather than specifying a hardcoded parameter - MatchWon(whoWon As Integer).
Public Class TableTennisMatch Public Event MatchWon As MatchWonHandler
The use of a delegate and the AddHandler statement gives you extra flexibility. Although this example is a little 'forced' to match the table tennis metaphor I'm using, you could add two event handlers now, one for the players and another to signal that the table is available for another match. Since the first parameter will signal which player won, I added another parameter for the table.
Public Delegate Sub MatchWonHandler( ww As Integer, tt As Integer)
Then both handlers have to be added, not just one. The AddHandler statement is a little bit unusual in the way it works here. A linked list of all handlers that have been added is maintained by VB.NET and executed one at a time. In some cases, this can be a problem because later handlers won't run until earlier ones finish and this can be a blocking problem. I'll cover this a bit more in the second part of this series.
AddHandler theMatch.MatchWon, AddressOf Me.Record_WinLoss AddHandler theMatch.MatchWon, AddressOf Me.UpdateTable
The RaiseEvent statement has to be modified.
RaiseEvent MatchWon(0, Table)
Finally, another event processing subroutine has to be present. In this case, I just added text to a label, but a complete system to update a "table" database and possibly raise another event could be coded here.
Public Sub UpdateTable( ByVal ww As Integer, ByVal tt As Integer) lblUpdateTable.Text = "Table " & tt & vbCrLf & " is available." End Sub
Click Here to display the illustration
VB.NET actually handles all events in pretty much the same way, but with most options, much of the code that makes it work is added by the compiler and isn't shown. You can add your own code to replace this automatic code, but understanding how it ties together isn't obvious ... and (Surprise!) Microsoft's documentation doesn't help a lot. Part Two of this series covers some of these mysteries and explains how the "Custom" keywork in declaring your event changes things.
To continue this series: Using the Custom Event Keyword