Vb.net 最佳实践-表单类从类模块接收消息

Vb.net 最佳实践-表单类从类模块接收消息,vb.net,winforms,code-standards,Vb.net,Winforms,Code Standards,希望得到一些关于在表单上捕获实例化类返回的消息的最佳实践建议 在我的表单(form1.vb)中,我有一个反映正在进行的操作的标签,代码如下 在form1.vb中编码以显示消息: Public Sub DisplayMessage(ByVal Msg as String, ByVal Show as Boolean) Application.DoEvents() If Show Then lblShow.Text = Msg lblShow.Refr

希望得到一些关于在表单上捕获实例化类返回的消息的最佳实践建议

在我的表单(form1.vb)中,我有一个反映正在进行的操作的标签,代码如下

在form1.vb中编码以显示消息:

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
到目前为止,我遇到了三种方法:

  • 直接表单调用。在这种情况下,类直接调用表单的消息例程:

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

  • 在类内引发事件。在本场景中,form1是发送消息的类的事件的好友,类将事件引发到表单中。


  • 有一个EventArgs类来处理事件。在这种情况下,我们有一个EventArg.vb类,每当我们引发事件时都会实例化该类。




  • 场景1似乎是最简单的,尽管有人建议我不要这样做,并要求我提出一个事件。随着时间的推移,我遇到了场景3,它与场景2相比增加了一个类

    因此,问题是。。。 在这三种方法之间,将消息从类返回到表单的“正确”方式是什么?场景3中的额外EventArg类是否必要,因为场景2也可以正常工作


    非常感谢。

    我的回答不是上述任何一项。考虑这个例子

    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
    
    您有一个窗体和一个类,窗体有一个对该类的引用(该类甚至不知道该窗体存在)。您的业务逻辑在类中执行,表单用于输入和显示信息
    CountTo1000()
    直接从表单调用,这是不好的,因为基本上UI线程处于休眠状态1000次,而类在每次休眠后试图通过引发事件来更新UI。但是UI从来没有时间允许事件发生,即更新。在
    Me.Label1.Text=number.ToString()
    之后放置
    Application.DoEvents()
    ,将允许用户界面更新。但这是糟糕设计的征兆。不要那样做

    这里是另一个多线程的例子

    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
    
    这个简单的例子展示了如何创建线程并在UI上运行一些代码。执行此操作时,如果必须访问UI控件(Label1),则必须在UI上调用来自非UI线程的任何方法调用。程序运行平稳,因为
    线程.Sleep
    是在与UI线程不同的线程上完成的,不需要
    应用程序.DoEvents
    ,因为UI线程在其他方面什么也不做,并且可以处理由另一个线程引发的事件

    我更关注线程,但在这两个示例中,设计都有一个带有类的表单,表单知道类,但类不知道表单。我们可以看到更多关于这方面的信息

    另见:

    • 为什么我们需要检查InvokeRequired,然后调用:
    • 比现在的
      线程
      更好的选择:
    • 更酷的选择,如果你能把你的头围绕着它:

    “最佳实践”取决于具体情况。这三种形式中的任何一种都可以根据用例工作。但是,
    Application.DoEvents
    、默认表单实例和非标准事件签名并不是任何最佳实践最坏实践的一部分:除非您是从不同的线程运行,否则您应该真正能够执行
    lblShow.Text=Msg
    。首先为什么要使用
    应用程序.DoEvents
    刷新
    ?引发和处理事件与调用方法没有太大区别。如果在窗体持有引用的单独类中运行,那么您可能希望从该类引发事件,因为该类无法调用窗体上的方法。如果您是多线程的,即另一个类在非UI线程上执行长时间运行的方法,则需要在窗体的事件处理程序中处理标签更新的调用。因此,没有更多信息,答案并不简单。如果可以的话,我很乐意帮助更多人。@djv-干杯。我得到了这个想法,并将在我的代码中实现它。目前我开发的工具非常简单(通常是一个按钮,顺序脚本执行),但我对我的编码方式不满意。我会投票支持你的回答,但我似乎还没有投票权……现在有一个比Thread更好的选择:BackgroundWorker-在引入
    async await
    后,它已经过时了
        **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
    
    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
    
    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