KnowDotNet

Use a Delegate for CallBack In Place of RaiseEvents

Static or Shared Methods Can't RaiseEvents

by Les Smith

How can I Raise an Event from a static or Shared method?  I get an error saying that I must have an Instance Method to use RaiseEvent.

We have a couple of excellent articles on raising events written by Brian Davis and Bill Ryan on this site,
see Brian's Article, but my problem is that I am using a class of Shared methods (would be static in C#) in a VB.NET application.  I wanted to use RaiseEvents to inform the Form that called the Shared method so that the form could update it's progress bar.  However, if you try to RaiseEvent from a Shared method, you will get a compile error basically saying that you must have an instance object to use RaiseEvents.

So, I used Delegates instead, to accomplish a CallBack.  This allows me to do the same things as RaiseEvent.  The code for this article wil show you how to do this.  You need to set up code in the calling form and in the Shared method of the class that is performing work for the form and periodically calling back to the form to allow it to display status.

The following code is placed in the calling Form.  First, in the Declarations section of the form, I placed the Delegates as shown below:

   Public Delegate Sub ScanStart(ByVal piTotal As Integer)
  
Public Delegate Sub ScanUpdate(ByVal piCurrItem As Integer)

Next, I create methods to handle the Delegates.  These methods are in the form also.

   Private Sub HandleScanStart(ByVal piTotal As Integer)
      
Me.pbProgress.Value = 1
      
Me.pbProgress.Maximum = piTotal
      
Me.pbProgress.Visible = True
   End Sub
   Private Sub HandleScanUpdate(ByVal piCurrItem As Integer)
      
Me.pbProgress.Value = piCurrItem
      DoEvents()
  
End Sub
   Private Sub HandleScanDone()
      
Me.pbProgress.Visible = False
   End Sub

Finally, I place code in the Form_Load event to set up the Delegates and call the Shared method to do work for me.  Since the call to the Shared method will take a few seconds, I am showing the form in the form_load event so that the user will see the form and see the progress of the process being performed by the class.  If I don't do this, the user might think the application has stopped responding.

      ' now show me so the progress bar will show
      Me.Show()
      DoEvents()
      
Dim ss As ScanStart
      ss =
AddressOf HandleScanStart
      
Dim su As ScanUpdate
      su =
AddressOf HandleScanUpdate

      Sessions.RefreshVariables(ss, su)
      
Me.HandleScanDone()

The code shown above is creating the Delegates with the address of the methods that will handle the Delegate events.  Once this is done, I simple pass the Delegate objects to the RefreshVariables method so that it can call back to the form.  The code in RefreshVariables is shown below.  First, the method will call back to the ScanStart Delegate, using the "ss" Delegate object reference.  This will pass the total number of ProjectItems so the HandleScanStart method in the form can initialize the ProgressBar.  

I have passed the Delegate objects as Optional parameters in this case because the RefreshVariables method is called from places that do not use a ProgresBar.  Using Optional parameters is probably to the chagrin of my C# friends, but in C#, I would simply overload the method.  By the way, the more C# programming I do, I find myself using Optional parameters when coding in VB.NET.  Giving up Optional parameters is not a real hardship and it sure makes it easier to use DLLs written in VB, and called from C#, when Optional parameters are not being used.  In fact, I have had to go back and remove the Optionals in order to facilitate calls from C#.

   Public Shared Function RefreshVariables( _
    
Optional ByVal ss As frmRefreshSessionVariables.ScanStart = Nothing, _
    
Optional ByVal su As frmRefreshSessionVariables.ScanUpdate = Nothing) _
    
As Boolean

              
' do some work to get the ProjectItems.Count

      ss.Invoke(prj.ProjectItems.Count)

Next, inside a loop that is looping through the ProjectItems, I will call the ScanUpdate Delegate with the number of the item currently being processed.

     If Not su Is Nothing Then
        piCurCount += 1
        su.Invoke(piCurCount)
    
End If

Again, because I used Optional parameters, I must check to see if the Delegate was supplied on the call.  That's it!  I have accomplished the same results using Delegates instead of RaiseEvents, albeit, it may take a couple of more lines of code, which is no big deal.