从类中的串行端口访问 WinForms 控件

Posted

技术标签:

【中文标题】从类中的串行端口访问 WinForms 控件【英文标题】:Accessing WinForms Controls from a serial port in a class 【发布时间】:2013-11-11 23:52:38 【问题描述】:

首先,道歉:我是新来在这个网站上发布问题的,所以对于格式或信息错误,我深表歉意。我已经看到了许多从串行端口获取数据并使用它来填充文本的答案主窗体上的框、图表等,使用“Invoke”,因为串行端口在不同的线程中运行。

我正在尝试将我们一直使用的一些通信内容“概括”到一个类中(是的,旧的 VB6 程序员正在努力成长:-) 我遇到了问题。如果我在主 program.cs 中强制使用表单名称并为类使用相同的命名空间,我可以做一些事情,但这有点违背了目的。我还尝试在类中的串行端口的“已接收”上添加一个事件,以在主窗体上引发一个事件。该事件试图引发,但发生跨线程异常。

此时的代码相当大,所以我将尝试“概述”它。以简单的形式,假设我有一个名为“Form1”的 for,其中包含一个名为 textbox1 的文本框和一个名为“SerialThing”的类:


表格1:

SerialThing mySerialThing ;

Form1_Load:

mySerialThing = new SerialThing();

显示数据()

Textbox1.Text = "You Got Data!";

SerialThing:

Static SerialPort myDevice;

初始化()

myDevice = new SerialPort;
myDevice.DataReceived += new SerialDataReceivedEventHandler(devicePort_DataReceived);

devicePort_DataReceived()

this.Invoke(new EventHandler(DisplayData));

如果串行端口放置在主窗体上,上述方法将起作用,但如果在类中创建,则不会。

再次,如果太复杂或太简单,请见谅。我正在寻找一种“简单”的方法来做到这一点,但要保持类“通用”(理想情况下不必让工作区名称匹配等)。

-Vin

【问题讨论】:

好吧,如果 this 是一个类而不是 Form 对象,那么 this.Invoke() 就不会很好地工作。您的课程没有 Invoke 方法。改为使用事件,以便表单类仍然可以在其事件处理程序中调用 this.Invoke() 来处理所需的编组。 【参考方案1】:

有很多很多方法可以做到这一点。我将介绍使用自定义事件、委托和 Invoke() 的经典方法,因为我认为理解该过程很重要。完成此操作后,您可以跳到一些较新的方法。

首先,在您的 SerialThing() 类中,您声明一个自定义事件以在收到数据时传递数据:

class SerialThing


    public delegate void DataReceivedDelegate(string data);
    public event DataReceivedDelegate DataReceived;

    static SerialPort myDevice;

    public SerialThing()
    
        myDevice = new SerialPort();
        myDevice.DataReceived += new SerialDataReceivedEventHandler(myDevice_DataReceived);
    

    void myDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
    
        // ... grab the data and place into a string called "data" ...
        string data = "";

        // raise our custom event:
        if (DataReceived != null)
        
            DataReceived(data);
        
    


现在,在 Form1 中,您在创建 SerialThing 实例时订阅该自定义事件。此外,当接收到该事件时,您可以使用 InvokeRequired、Invoke 和委托将来自辅助线程的调用编组到主线程:

public partial class Form1 : Form


    SerialThing mySerialThing;

    public Form1()
    
        InitializeComponent();
    

    private void Form1_Load(object sender, EventArgs e)
    
        mySerialThing = new SerialThing();
        mySerialThing.DataReceived += new SerialThing.DataReceivedDelegate(mySerialThing_DataReceived);
    

    private delegate void DataReceivedDelegate(string data);

    void mySerialThing_DataReceived(string data)
    
        if (this.InvokeRequired)
        
            this.Invoke(new DataReceivedDelegate(mySerialThing_DataReceived), new Object[]  data );
        
        else
        
            textBox1.Text = data;
        
    


编辑:响应您的 cmets 下面...

将委托简单地视为“指向方法的指针”。当您执行委托时,关联的方法就会运行。

InvokeRequired() 部分确定代码是否在与创建控件的线程不同的线程中运行。在这种情况下,控件是表单本身 (this)。如果返回 true,则事件是在不同的线程中接收的。然后我们继续到If 块的真实部分内的this.Invoke() 行。同样this 指的是表单。因此,表单请求在创建它的线程(主 UI 线程)上调用(“运行”)传递的委托。我们创建了一个委托实例,它实际上指向了我们已经在导致递归调用的相同方法。第二个参数只是一个 Object 数组,用于与委托一起传递参数。

当 Invoke() 运行时,由于递归调用,我们最终会重新进入该方法。然而,此时 InvokeRequired() 检查将返回 false,因为我们现在正在主 UI 线程中运行。因此,我们下拉到 If 语句的错误部分,我们在其中更新 TextBox。在这种模式下,可以安全地更新 If 语句的 else 块中的 GUI 控件。

请注意,这里不需要递归调用。这只是一种风格选择。我们本可以使用委托所指向的第二个“帮助”函数,然后调用它。递归方法减少了所需方法的数量。

这可能是解决此类问题的最冗长的方法。不过我喜欢它,因为它显示了事件和数据的流动,以及线程之间的移动。

我们可以使用匿名委托将所有表单代码缩短到这样:

    private void Form1_Load(object sender, EventArgs e)
    
        mySerialThing = new SerialThing();
        mySerialThing.DataReceived += delegate (string data)
        
            this.Invoke((MethodInvoker)(delegate()  textBox1.Text = data; ));
        ;
    

我不了解你,但作为一名前 VB6 程序员,当你第一次看到这种类型的东西时,这看起来很奇怪。

我还使用了我知道运行不同的组件 线程,但“表单代码”从未使用委托的东西, 所以也许有一些东西可以埋在课堂上?

是的,可以将一些“魔法”融入到一个类中,这样它就可以在主 UI 线程上引发事件,因此不需要任何 Invoke() 调用。一种方法是使用SynchronizationContext。

解决此类问题的另一种可能性是使用 BackgroundWorker() 控件,该控件具有在主 UI 线程中为您引发的诸如 ProgressChanged() 和 RunWorkerCompleted() 之类的事件(它们执行必要的调用类型的东西在引擎盖下为您服务)。

【讨论】:

感谢以上所有。昨晚,我按照示例将其拼凑到我现有的代码中,并且今天能够在办公室对其进行测试。它完美地工作!谢谢!!现在我想我需要阅读有关代表的信息,因为我不确定它究竟是如何或为什么起作用的,以及为什么只是事件不起作用。我还使用了我知道的在不同线程中运行的组件,但“表单代码”从来不必使用委托的东西,所以也许有一些东西可以埋在类中? 另外,您提到这是一个很好的“基础知识”,但还有其他方法。是否有很好的参考来查看不同的方式并比较/对比它们?再次感谢您的彻底帮助! (基于编辑)哇!这是一个很好的解释。非常感谢。是的,我更喜欢自己看流程。我主要是嵌入式设计师(小型微型,8-32 位)硬件和固件(C 语言)。我的桌面经验一直是设计嵌入式系统的“前端”:校准、数据上传等)并且不会是设计企业系统的人(尽管我们收到越来越多的实时发布请求这些天数据到网站)。我在 15 年前选择了 VB6,并在大约 3 年前切换到 C#。我主要对嵌入式系统进行串行通信。非常感谢! 很高兴看到有人和我一样追随潮流。作为一名嵌入式程序员,我根本无法理解诸如委托和事件之类的更高层次的想法。我进行 PC 编码的唯一原因是我的嵌入式代码的串行端口应用程序或程序员应用程序:)

以上是关于从类中的串行端口访问 WinForms 控件的主要内容,如果未能解决你的问题,请参考以下文章

为啥从类中访问类变量需要“自我”。在 Python 中? [复制]

从类中访问时间线变量?

从类访问 datagridview 中的数据

为啥 Invoke 在另一个类中不起作用

从 VBA 访问串行端口的最佳方法是啥?

找出从后台线程访问的 winforms 控件