首先,道歉:我很想在这个网站上发布问题,所以我为格式化或信息错误道歉。我已经看到很多答案从表格中删除数据并使用它来填充文本主窗体上的框,图形等,使用“调用”,因为串口在不同的线程中运行。
我试图“概括”我们一直用于某个类的一些通信内容(是的,旧的VB6程序员正在努力成长:-)而且我遇到了问题。如果我在主program.cs中强制使用表单名称并为该类使用相同的命名空间,我可以做一些事情,但是这种方法会失败。我也尝试在类中的“接收”甚至串口上添加一个事件来在主窗体上引发一个事件。事件尝试引发但发生了交叉线程异常。
此时的代码非常大,所以我会尝试“勾勒”它。以简单的形式,假设我有一个名为“Form1”的for,其中包含一个名为textbox1的文本框和一个名为“SerialThing”的类:
SerialThing mySerialThing ;
mySerialThing = new SerialThing();
Textbox1.Text = "You Got Data!";
Static SerialPort myDevice;
myDevice = new SerialPort;
myDevice.DataReceived += new SerialDataReceivedEventHandler(devicePort_DataReceived);
this.Invoke(new EventHandler(DisplayData));
如果串行端口放在主窗体上,则上述操作将起作用,但如果在类中创建则不行。
再次,抱歉,如果太复杂,或太简单。我正在寻找一种“简单”的方法来做到这一点,但保持这个类“通用”(理想情况下不必将工作区名称匹配等)。
-Vin
答案 0 :(得分:1)
有许多方法可以做到这一点。我将使用自定义事件,委托和Invoke()来呈现经典方法,因为我认为理解该过程很重要。一旦你失败了,你可以跳到一些新的方法。
首先,在SerialThing()类中,声明一个Custom事件以在收到数据时传递数据:
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;
}
}
}
编辑:回应你的评论......
将委托简单地视为“指向方法的指针”。执行委托时,将运行关联的方法。
InvokeRequired()部分确定代码是否在与创建控件的线程不同的线程中运行。在这种情况下,控件是表单本身(this
)。如果返回true,则在另一个线程中接收该事件。然后,我们前往this.Invoke()
块的真实部分内的If
行。同样this
指的是表格。因此,Form请求在创建它的线程(主UI线程)上调用(“运行”)传递的委托。我们创建一个委托实例,它实际指向我们已经存在的同一个方法,从而产生一个递归调用。第二个参数只是一个Object数组,用于传递参数和委托。
当运行Invoke()时,由于递归调用,我们最终重新进入该方法。但是,此时,InvokeRequired()检查将返回false,因为我们现在正在主UI线程中运行。因此,我们将下拉到我们更新TextBox的If
语句的错误部分。在此模式中,可以安全地更新else
语句的If
块中的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()(它们执行必要的调用类型的东西在引擎盖下为你)。