C# 从类中的串行端口访问WinForms控件
首先,道歉:我对在这个网站上发帖很陌生,所以我对格式或信息错误表示歉意。我看到过很多关于从表单上的串行端口获取数据并使用它填充主表单上的文本框、图形等的答案,使用“调用”是因为串行端口运行在不同的线程中 我试图将我们一直在使用的一些通信工具“概括”到一个类中(是的,老VB6程序员正试图长大:-),但我遇到了一些问题。如果我在main program.cs中强制使用一个表单名,并为类使用相同的名称空间,我可以做一些事情,但这种做法无法达到目的。我还尝试在类中串行端口的“received”上添加一个事件,以便在主窗体上引发一个事件。事件试图引发,但发生跨线程异常 此时的代码相当大,因此我将尝试“概述”它。简单来说,假设我有一个名为“Form1”的for,其中包含一个名为textbox1的文本框和一个名为“SerialThing”的类:C# 从类中的串行端口访问WinForms控件,c#,serial-port,C#,Serial Port,首先,道歉:我对在这个网站上发帖很陌生,所以我对格式或信息错误表示歉意。我看到过很多关于从表单上的串行端口获取数据并使用它填充主表单上的文本框、图形等的答案,使用“调用”是因为串行端口运行在不同的线程中 我试图将我们一直在使用的一些通信工具“概括”到一个类中(是的,老VB6程序员正试图长大:-),但我遇到了一些问题。如果我在main program.cs中强制使用一个表单名,并为类使用相同的名称空间,我可以做一些事情,但这种做法无法达到目的。我还尝试在类中串行端口的“received”上添加一个
表格1: 表1\u荷载: DisplayData()
连载: Init() devicePort_数据接收()
如果串行端口放在主窗体上,则上述操作将起作用,但如果在类内部创建,则不起作用 再次,如果太复杂或太简单,请道歉。我正在寻找一种“简单”的方法来实现这一点,但要使类“通用化”(理想情况下不必让工作区名称匹配,等等)
-Vin有很多很多方法可以做到这一点。我将介绍使用自定义事件、委托和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;
}
}
}
编辑:为了回应你下面的评论
将委托视为“指向方法的指针”。执行委托时,关联的方法将运行
invokererequired()部分确定代码是否运行在与创建控件的线程不同的线程中。在本例中,控件是表单本身(this
)。如果返回true,则事件是在不同的线程中接收的。然后,我们继续执行If
块的真实部分内的this.Invoke()
行。同样地,此
指的是表单。因此,表单请求在创建它的线程(主UI线程)上调用(“运行”)传递的委托。我们创建一个委托实例,该实例实际上指向我们已经在生成递归调用的相同方法。第二个参数只是一个对象数组,用于随委托传递参数
在运行Invoke()时,由于递归调用,我们最终会重新输入该方法。但是,此时,invokererequired()检查将返回false,因为我们现在正在主UI线程中运行。因此,我们将下拉到If
语句的false部分,在那里更新文本框。在此模式中,可以安全地更新If
语句的else
块中的GUI控件
注意,这里不需要递归调用。这只是一种风格选择。我们可以使用代理指向的第二个“helper”函数,并调用它。递归方法减少了所需方法的数量
这可能是解决这类问题最冗长的方法。不过,我喜欢它,因为它显示了事件和数据流,以及线程之间的移动
我们可以使用匿名委托将所有表单代码缩短为:
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()调用。实现这一点的一种方法是使用
处理此类问题的另一种可能性是使用BackgroundWorker()控件,该控件具有在主UI线程中为您引发的ProgressChanged()和RunWorkerCompleted()等事件(它们为您在后台执行必要的调用类型操作)。好吧,这是.Invoke()如果这是一个类而不是表单对象,则不会很好地工作。您的类没有Invoke方法。改为使用事件,以便form类仍然可以在其事件处理程序中调用this.Invoke(),以处理所需的封送处理。感谢您所做的所有这些。我昨晚在家里按照这个例子把它拼凑到现有的代码中,今天在办公室测试了它。它工作得很好!谢谢现在我想我需要了解一下代表们的情况,因为我不确定它到底是如何工作的,为什么工作,为什么只是活动没有。我也使用过我知道可以在不同线程中运行的组件,但是“表单代码”从来没有使用过委托的东西,所以可能有些东西可以嵌入到类中?另外,你提到过这是一个很好的“基础知识”,但还有其他方法。是否有一个很好的参考来看待不同的方法并比较/对比它们?再次感谢您的全力帮助!(基于编辑)哇!这是一个很好的解释。非常感谢。是的,我更喜欢自己去看电影。我主要是一个嵌入式设计
Static SerialPort myDevice;
myDevice = new SerialPort;
myDevice.DataReceived += new SerialDataReceivedEventHandler(devicePort_DataReceived);
this.Invoke(new EventHandler(DisplayData));
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);
}
}
}
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;
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
mySerialThing = new SerialThing();
mySerialThing.DataReceived += delegate (string data)
{
this.Invoke((MethodInvoker)(delegate() { textBox1.Text = data; }));
};
}