65.9K
CodeProject 正在变化。 阅读更多。
Home

Winforms 中异步事件驱动控件更新

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2020年10月14日

公共领域

4分钟阅读

viewsIcon

10350

downloadIcon

242

使用后台工作程序和事件驱动触发器更新控件

引言

尽管它看起来可能过时了,但通过 RS232、RS485 或虚拟 USB COM 端口进行的串行通信在行业中仍然发挥着作用。 无论是与 CNC 机器或其他工业机械交换数据,还是通过 Modbus 与 HVAC 设备或电器进行接口,串行通信都是隐藏但无处不在的。

在设计 SCADA 或图形面板的用户界面时,确保不断向用户返回正确的视觉更新至关重要,即使是在漫长而痛苦的数据交换会话期间,或在执行耗时操作时也是如此。 标签、图片框、仪表和其他控件需要在后台不断更新。

我的同事经常问的一个功能是希望能够直观地显示通过串行线的 RX/TX 数据,因为大多数廉价的串行适配器和接口都没有任何数据 LED。

本文将展示如何使用后台工作线程来更新模拟红绿 LED 的两个图片框的 UI。 当然,这个概念可以扩展到所有需要异步更新的视觉控件。

设置

逐步演练

我们将从设置表单开始。 对于此示例,我们只需要一个包含两个图片框的表单,一个用于 TX LED,另一个用于 RX LED。 我们还将它们的可见性设置为 False,并将它们调整大小以正确地模拟表单左上角的两个 LED。 我们将它们命名为 pbSend (TX) 和 pbReceive (RX)。

下一步是声明所需的委托和后台工作线程。 UI 更新必须在主线程中进行,并且由于后台工作线程在主线程以外的线程上运行,因此我们将设置一个委托来为我们执行此操作。

我们还将声明后台工作线程本身,一个用于 TX LED,另一个用于 RX LED。

    Private WithEvents sp As New SerialPort
    Private Delegate Sub LEDSwitchDelegate(ByRef a As PictureBox, ByVal b As Boolean)
    Private WithEvents backgroundWorker1 As New BackgroundWorker()
    Private WithEvents backgroundWorker2 As New BackgroundWorker()

提供打开和关闭两个 LED 的实际子程序如下所示。 我们将通过传递 picturebox 名称及其可见性作为参数来调用它。 例如

        LEDSwitch(pbSend, True)

它将导致 pbSend LED 被打开。 由于有委托,这将适用于主线程和任何其他线程!

    Private Sub LEDSwitch(ByRef a As PictureBox, ByVal b As Boolean)
        If Me.InvokeRequired Then
            Dim d As New LEDSwitchDelegate(AddressOf Me.LEDSwitch)
            Me.BeginInvoke(d, New Object() {a, b})
        Else
            Try
                a.Visible = b
            Catch ex As Exception
            End Try
        End If
    End Sub

在我的项目中,只要发送或接收到一个或多个字节,LED 就会亮起 500 毫秒,因此后台工作线程将运行确切的时间。

    Private Sub backgroundWorker1_DoWork(ByVal sender As Object, _
    ByVal e As DoWorkEventArgs) Handles backgroundWorker1.DoWork
        Thread.Sleep(500)
    End Sub
    Private Sub backgroundWorker2_DoWork(ByVal sender As Object, _
    ByVal e As DoWorkEventArgs) Handles backgroundWorker2.DoWork
        Thread.Sleep(500)
    End Sub

它们不会做太多事情,除了等待 500 毫秒(在这里,您的经验可能会有所不同,并且可以更好地利用后台工作线程浪费的周期)。

工作完成后(只是一个 500 毫秒的非阻塞暂停),LED 将再次关闭。 我们将利用与两个后台工作线程中的每一个相关联的 RunWorkerCompleted 事件。

    Private Sub backgroundWorker1_RunWorkerCompleted(ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) Handles backgroundWorker1.RunWorkerCompleted
        LEDSwitch(pbSend, False)
    End Sub
    Private Sub backgroundWorker2_RunWorkerCompleted(ByVal sender As Object, _
    ByVal e As RunWorkerCompletedEventArgs) Handles backgroundWorker2.RunWorkerCompleted
        LEDSwitch(pbReceive, False)
    End Sub

好的,我们关闭了 LED,但我们如何首先打开它们呢,例如在接收到数据时?

我们将为此使用一个专用的 Sub,并将该 Sub 绑定到 Serialport 类实例的 DataReceived 事件。 由于 TransmittingData() 在主线程上,我们可以直接打开 LED。 我们确保后台工作线程尚未繁忙,以避免多次调用它们。

    Private Sub TransmittingData()
        LEDSwitch(pbSend, True)
        If Not backgroundWorker1.IsBusy Then
            ' Start the asynchronous operation.
            backgroundWorker1.RunWorkerAsync()
        End If
    End Sub
    Private Sub ReceivingData()
        LEDSwitch(pbReceive, True)
        If Not backgroundWorker2.IsBusy Then
            ' Start the asynchronous operation.
            backgroundWorker2.RunWorkerAsync()
        End If
    End Sub

我们需要的最后一个 Sub 只是 form_Load 方法,我们在其中添加了 DataReceived 事件的事件处理程序并将其绑定到 ReceivingData()

    Private Sub AsynchPictureboxes_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        AddHandler sp.DataReceived, AddressOf ReceivingData
    End Sub

不幸的是,System.IO.Ports.SerialPort 类不包含 DataSent 事件,因此我们无法直接绑定它,但很容易克服这个事实,即每当需要传输一个或多个字节时,我们可能会已经有一个 Sub 了。 我们将使用调用 Serialport.Write 方法的 Sub 来调用 TransmittingData() Sub

正如所见,我们不需要担心关闭 LED。 后台工作线程会处理这个问题。

进一步开发

可以扩展这些方法

  1. 可以显示和识别端口或数据错误,例如,两个 LED 同时快速闪烁。 在这种情况下,我们可以按如下方式绑定事件,并编写一个特定的 DataError 方法来处理此类情况。
    AddHandler sp.ErrorReceived, AddressOf DataError
  2. 可以开发更多方法来显示,例如 CRC、校验和错误等。
  3. LED 的闪烁频率可以通过编程方式更改,方法是将适当的脉冲时间传递给后台工作线程,以响应特定消息或数据报。
  4. 可以由后台工作线程通过编程方式填充一个或多个组合框和文本框,例如,实时从串行通信传入的数据,而不会让主线程保持繁忙且无响应。

结论和关注点

本文旨在展示一种快速而简单的方法,用于从后台工作线程更新 UI,尤其是在与低数据速率传输或可能使 UI 繁忙的耗时操作结合使用时。 提供的示例可以轻松扩展和修改,以满足仍在处理 Winforms 技术的程序员的需求,并且需要以编程方式和频繁地更新表单中的多个控件,而不会阻止用户与应用程序界面的交互。

历史

  • 2020 年 10 月 14 日:初始版本
© . All rights reserved.