KnowDotNet

Raising and Handling Custom Events

by Brian Davis

You can use events in .NET to signal consumers of an object when certain processes occur.  I often use events to signal a status change in a long running process.  Consider a simple example of an application that will handle moving all the files in one directory to another.  This operation could take a long time, depending on the number of files in the directory.  It would be nice to inform the user of the status of the operation and notify them each time a file is moved so that they know what is happening.

You could accomplish this by having all the code for the operation within a single form, updating a StatusBar on the form as each file is moved.  The problem with this, of course, is that you tie the file-moving code to the user interface and prevent it from being used easily by other classes or forms.  We obviously want the file-moving code to be inside a class.  You could pass an instance of the form into the class, but this is just as bad as having the code inside the class.  The answer lies in using events.  The FileMover object can just raise an event when it moves a file, and then anyone who cares about it can just handle that event.  This is much more elegant than passing forms or making calls to other classes to update a global status message.  This separates the raising or the actual event from how it is handled.  And best of all, the code is quite simple.

Just declare an event within the class like this:

   Public Event StatusChanged(ByVal Message As String)

Now, whenever you want any consumers of your object to be notified of a status change, call RaiseEvent like this:

   RaiseEvent StatusChanged("File " & OldFile & " moved to " & NewFile)

Perhaps you want to signal something a bit more specific.  What if some caller of yours wants to know what files were moved without parsing them out of your message in the StatusChanged event.  No problem - just raise another event:

   Public Event FileMoved(ByVal OldFile As String, ByVal NewFile As String)
  
RaiseEvent FileMoved(OldFile, NewFile)

Here is the resulting class for moving all files in a directory to another directory:

Imports System.IO

Public Class FileMover

  
'Generic Event to signal status change
   Public Event StatusChanged(ByVal Message As String)

  
'Specific Event to signal file moved
   Public Event FileMoved(ByVal OldFile As String, ByVal NewFile As String)

  
Public Sub MoveFiles(ByVal OldPath As String, ByVal NewPath As String)
      
'Moves all files in OldPath to NewPath

      'Make sure NewPath has a trailing "\"
      If Not NewPath.EndsWith("\") Then NewPath &= "\"

      
'Get an array containing all files in OldPath
      Dim OldFiles() As String = Directory.GetFiles(OldPath)

      
'Move each file from OldPath to NewPath
      For Each OldFile As String In OldFiles
        
Dim NewFile As String = NewPath & Path.GetFileName(OldFile)
         File.Move(OldFile, NewFile)
        
RaiseEvent StatusChanged("File " & OldFile & " moved to " & NewFile)
        
RaiseEvent FileMoved(OldFile, NewFile)
      
Next

   End Sub

End
Class

Now here is code from a form with a StatusBar, a Button, and 2 TextBoxes showing how this class might be used:

...
   Private OldFiles As New ArrayList
   Private NewFiles As New ArrayList
  
Private WithEvents MyFileMover As New FileMover

  
Private Sub UpdateFileList(ByVal OldFile As String, _
                              
ByVal NewFile As String) _
                              
Handles MyFileMover.FileMoved

      
'Update the file lists
      Me.OldFiles.Add(OldFile)
      
Me.NewFiles.Add(NewFile)

  
End Sub

   Private Sub UpdateStatus(ByVal Message As String) _
                            
Handles MyFileMover.StatusChanged

      
'Update the status message in the StatusBarPanel1
      Me.StatusBarPanel1.Text = Message

  
End Sub

   Private Sub btnMoveFiles_Click(ByVal sender As System.Object, _
                                  
ByVal e As System.EventArgs) _
                                  
Handles btnMoveFiles.Click

      
'Call on MyFileMover to move files
      MyFileMover.MoveFiles(Me.tbOldPath.Text, Me.tbNewPath.Text)

  
End Sub
...

You can see how this approach can be extended to raise all kinds of events.  You could increment a ProgressBar control every time a certain action is performed, or use events to allow the user to cancel long-running processes.  Events allow you to design objects that aren't too closely linked to the user interface or to other objects.  This results in more modular and more portable code.