【问题标题】:Best Practices - Form Class Receiving Message from Class Modules最佳实践 - 表单类从类模块接收消息
【发布时间】:2017-09-14 01:10:58
【问题描述】:

希望就从我的表单上的实例化类捕获返回的消息获得一些最佳实践建议。

在我的表单 (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

到目前为止,我遇到了三种方法:

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

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


  2. 类内的RaiseEvent。 在这种情况下,form1 是发送消息的类的Friends WithEvents,该类将事件引发到窗体。

    **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. 让 EventArgs 类处理事件。 在这种情况下,我们有一个 EventArg.vb 类,它在我们引发事件时被实例化。

    **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


场景 1 似乎是最简单的,尽管有人建议我不要这样做,并要求我提出一个事件。随着时间的推移,我遇到了场景 3,其中涉及一个额外的类与场景 2。

因此,问题是... 在这三种方法之间,哪一种是将消息从类返回到表单的“正确”方式?由于场景 2 也可以正常工作,因此是否需要根据场景 3 额外的 EventArg 类?

非常感谢。

【问题讨论】:

  • “最佳实践”视情况而定。这三种形式中的任何一种都可以根据用例起作用。但是,Application.DoEvents,默认表单实例和非标准事件签名不是任何最佳实践的一部分
  • 最坏做法:Application.DoEvents()
  • 你真的应该能够做到lblShow.Text = Msg,除非你从不同的线程运行。为什么是Application.DoEventsRefresh
  • 引发和处理事件与调用方法没有太大区别。如果在表单持有引用的单独类中运行,那么您可能希望从该类引发事件,因为该类无法调用表单上的方法。如果您是多线程,即另一个类在非 UI 线程上执行长时间运行的方法,您将需要在表单的事件处理程序中处理标签更新的调用。因此,如果没有更多信息,答案并不简单。如果可以的话,我很乐意提供更多帮助。
  • @djv - 干杯。我明白了这个想法,并将在我的编码中实现它。目前我开发的工具都是非常简单的东西(通常是一个按钮,顺序脚本执行),但我对我的编码方式并不满意。我会投票给你的回复作为答案,但我似乎还没有投票权....

标签: vb.net winforms code-standards


【解决方案1】:

以上都不是我的回答。考虑这个例子

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() 将允许UI 更新。但这是糟糕设计的症状。不要那样做。

这是另一个多线程的例子

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 线程的任何方法调用。程序运行顺利,因为Thread.Sleep 是在与 UI 线程不同的线程上完成的,不需要Application.DoEvents,因为 UI 线程在其他方面什么都不做,并且可以处理由另一个线程引发的事件。

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

另见:

【讨论】:

  • 现在比 Thread 更好的选择:BackgroundWorker - 在引入 async-await 后已过时
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-10
  • 2012-01-24
  • 1970-01-01
  • 2014-03-02
  • 2013-11-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多