vb.netwinformscode-standards

Best Practices - Form Class Receiving Message from Class Modules


Hoping to get some best-practice advise with regards to capturing a returned message from an instantiated class on my form.

In my form (form1.vb), I have a label which reflects what is being done, with the code below.

Code in form1.vb to display message:

Public Sub DisplayMessage(ByVal Msg as String, ByVal Show as Boolean)
    Application.DoEvents()
    If Show Then
        lblShow.Text = Msg
        lblShow.Refresh()
    End If
End Sub

I have came across three methods so far:

  1. Direct Form Call. In this scenario the class directly calls the form's message routine:

    form1.DisplayMessage("Show This Message", True)
    


  2. RaiseEvent within class. In this scenario form1 is Friends WithEvents of the class sending the message, and the class raises the event to the form.

    **Declared in Form1.vb**
    Friend WithEvents Class1 as New Class1      
    
    **Declared in Class1.vb**
    Public Event SetMessage(ByVal Msg As String, ByVal Show As Boolean)
    
    **Used in Class1.vb**
    RaiseEvent SetMessage("Show This Message", True)
    


  3. Have an EventArgs class handle the event. In this scenario we have an EventArg.vb class which is instantiated whenever we raise the event.

    **Declared in Form1.vb**
    Friend WithEvents Class1 as New Class1    
    
    Private Sub class1_DisplayMessage(ByVal Msg As String, ByVal showAs Boolean, ByRef e As ProgressMessageEventArgs) Handles Class1.SetMessage
        DisplayMessage(Msg, Show)
    End Sub
    


    **Declared in Class1.vb**
    Public Event SetMessage(ByVal msg As String, ByVal Show As Boolean, ByRef e As ProgressMessageEventArgs)

    Protected Sub CaptureMessage(ByVal msg As String, ByVal Show As Boolean)
        RaiseEvent SetMessage(message, ShowList, New ProgressMessageEventArgs(message))
    End Sub

    **Used in Class1.vb**
    RaiseEvent CaptureMessage("Show This Message", True)


    **EventArg.vb created to handle ProgressMessageEventArgs class**
    Public NotInheritable Class ProgressMessageEventArgs
        Inherits System.EventArgs
        Public txt As String

        Public Sub New(ByVal txt As String)
            MyBase.New()
            Me.Text = txt 
        End Sub
    End Class


Scenario 1 is seemingly the simplest, though I was advised against this and asked to raise an event instead. Over time I came across scenario 3 which involves an additional class vs scenario 2.

Therefore, the question is... Between these three methods, which would be the "proper" way of returning a message from a class to the form? Is the additional EventArg class as per scenario 3 necessary since scenario 2 works fine as well?

Many thanks in advance.


Solution

  • My answer is none of the above. Consider this example

    Public Class Form1
    
        Private WithEvents myClass1 As New Class1()
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            myClass1.CountTo1000()
        End Sub
    
        Private Sub MyClass1_Updated(number As Integer) Handles myClass1.Updated
            Me.Label1.Text = number.ToString()
        End Sub
    
    End Class
    
    Public Class Class1
    
        Public Event Updated(number As Integer)
    
        Public Sub CountTo1000()
            For i = 1 To 1000
                System.Threading.Thread.Sleep(1)
                RaiseEvent Updated(i)
            Next
        End Sub
    
    End Class
    

    You have a form and a class, and the form has a reference to the class (the class doesn't even know the form exists). Your business logic is performed in the class, and the form is used to input and display information. CountTo1000() is being called directly from the form, which is bad because basically the UI thread is being put to sleep 1000 times, while the class is trying to update the UI by raising the event after each sleep. But the UI never has time to allow the events to happen, i.e. to be updated. Placing an Application.DoEvents() after Me.Label1.Text = number.ToString() will allow the UI to update. But this is a symptom of bad design. Don't do that.

    Here is another example with multi-threading

    Public Class Form1
    
        Private WithEvents myClass1 As New Class1()
    
        ' this handler runs on UI thread
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            ' make a new thread which executes CountTo1000
            Dim t As New System.Threading.Thread(AddressOf myClass1.CountTo1000)
            ' thread goes off to do its own thing while the UI thread continues
            t.Start()
        End Sub
    
        ' handle the event
        Private Sub MyClass1_Updated(number As Integer) Handles myClass1.Updated
            updateLabel(number.ToString())
        End Sub
    
        ' invoke on UI thread if required
        Private Sub updateLabel(message As String)
            If Me.Label1.InvokeRequired Then
                Me.Label1.Invoke(New Action(Of String)(AddressOf updateLabel), message)
            Else
                Me.Label1.Text = message
            End If
        End Sub
    
    End Class
    
    Public Class Class1
    
        Public Event Updated(number As Integer)
    
        Public Sub CountTo1000()
            For i = 1 To 1000
                System.Threading.Thread.Sleep(1)
                RaiseEvent Updated(i)
            Next
        End Sub
    
    End Class
    

    This simple example shows how a thread can be created and run some code off the UI. When doing this, any method call from the non-UI thread must be invoked on the UI if it must access a UI control (Label1). The program runs smoothly since the Thread.Sleep is done on a different thread than the UI thread, with no need for Application.DoEvents, because the UI thread is otherwise doing nothing, and can handle the events being raised by the other thread.

    I focused more on threading, but in both examples the design has a form with a class, and the form knows about the class, but the class doesn't know about the form. More about that can be seen here.

    See also: