0

我在 Visual Studio 2010 中使用 C# 和 Winforms

我有一个程序,我试图通过串行端口读取输出并将其打印到屏幕上。它最初是作为一个控制台程序开始的,但现在已经发展到我们希望将输出放在表单上的字段中。我有代码可以解析出我正在寻找的输出的串行端口写入和工作,我只需将 Console.WriteLine 更改为 label.text = "";,基本上。我已将侦听串行端口的函数合并到 GUI 代码中,因此所有内容都在同一个文件中。

不过,我对如何让函数写入标签感到困惑。它是静态的,所以我不能只说'label.text ='。我尝试在要使用的函数内创建一个新的表单对象,这允许我访问表单上的控件,但不会更新我在运行时看到的表单(我猜是因为我创建了表单的新实例而不是访问现有实例?)

我还需要让串行侦听器与 GUI 同时运行,因此 GUI 标签将更新为接近实时运行该函数所获得的结果,因此我尝试将其设置为线程化,GUI 是由 main() 启动的一个线程,而串行侦听器是另一个线程,当我单击按钮启动它时启动它。但是,我遇到了同样的问题,无法访问串行侦听器线程中的标签,因为它必须是静态的才能使用 system.threading 进行初始化。

我在想也许我需要为串行侦听器使用后台工作人员,但我对这些的经验绝对为零。后台工作人员能否实时更新 GUI 上的标签?

我不能发布特定的代码,但继承人的一般想法:

Main() 启动 GUIthread

GUI 具有启动串行侦听器的按钮 OnClick 按钮启动 ListenerThread ListenerThread 输出到控制台,希望输出到表单标签

无法访问 GUI.Label,因为侦听器是静态的,无需线程化 在侦听器中创建新的 GUI 实例允许我调用该实例的控件,但它们不会在运行时更新 GUI

确保标签是公开的。

4

4 回答 4

2

BackgroundWorker门课基本上就是为此而生的。

只需让该DoWork方法完成您的实际工作,并确保ReportProgess在工作时根据需要调用该方法。您可以将任何数据作为 a string(或其他任何数据,如果您愿意)传递,然后在ProgressChanged事件处理程序中使用该值,表单可以处理该值来更新其 UI。

请注意,BackgroundWorker将自动确保ProgressChangedRunWorkerCompleted事件在 UI 线程中运行,因此您无需为此烦恼。

这是一个示例工人:

public class MyWorker//TODO give better name
{
    public void DoWork(BackgroundWorker worker)//TODO give better name
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(1000);//to mimic real work
            worker.ReportProgress(0, i.ToString());
        }
    }
}

这是配置后台工作人员的示例。在这里,我使用 lambdas 是因为能够关闭变量很方便(即在每个匿名方法中使用变量),但是如果您愿意,可以将每个事件处理程序重构为方法。

private void button1_Click(object sender, EventArgs e)
{
    var bgw = new BackgroundWorker();
    MyWorker worker = new MyWorker();

    bgw.WorkerReportsProgress = true;
    bgw.DoWork += (s, args) => { worker.DoWork(bgw); };
    bgw.ProgressChanged += (s, data) =>
    {
        label1.Text = data.UserState.ToString();
    };
    bgw.RunWorkerCompleted += (s, args) =>
    {
        label1.Text = "All Done!";
    };

    bgw.RunWorkerAsync();//actually start the worker
}

请注意,表单中的控件都不是公共的,它们都不是静态的,并且我没有在类之外传递对我的表单的任何引用。Form每个人都负责更新自己的控件被认为是最好的形式。您不应该允许其他任何人直接访问它们。与其允许其他工作人员类直接访问标签或修改其文本,工作人员只是简单地告诉表单,“嘿,我有一些数据,你可以根据这些值相应地更新自己。 " 然后是负责更新自身的表单。事件是您用来允许这些工作人员或其他类型的子元素(例如您创建的其他表单)通知“父”

于 2012-10-29T19:03:33.017 回答
0

我认为您可以使用后台工作人员,它们非常易于使用。

为了使用 BackgroundWorker,您必须至少实现两个事件:

backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

在那里你读了你的输入。它被触发调用 backgroundWorker1.RunWorkerAsync(...)

backgroundWorker1_ProgressChanged(....)

在那里你更新你的标签。也许您必须创建一个委托来更新它。

您还可以实现:

backgroundWorker1_RunWorkerCompleted(....)

让你知道它什么时候停止...

于 2012-10-29T18:48:31.033 回答
0

要写入任何 Windows 控件,您必须在 UI 线程上。如果您有一个在不同线程上运行的串行侦听器,那么您需要在更改窗口控件之前切换线程。BeginInvoke 可以很方便, http: //msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke.aspx

我要做的是向串行侦听器添加一个 Action,每当侦听器想要显示某些内容时就会调用它。然后这个 Action 会调用 BeginInvoke。

就像是:

static class SerialListner
{
    public Action<string> SomethingToDisplay;

    void GotSomethingToDisplay(string s)
    {
        SomethingToDisplay(s);
}

然后在你的窗体中的某个地方

SerialListern.SomethingToDisplay = (s) => 
   label.BeginInvoke((Action) () => label.Text = s);
于 2012-10-29T18:46:58.567 回答
0

继续您所说的静态侦听器方法并且它曾经是一个控制台应用程序,我认为一个相对较小的修改可能如下:

class Program
{
    static void Main(string[] args)
    {
        // Create a main window GUI
        Form1 form1 = new Form1();

        // Create a thread to listen concurrently to the GUI thread
        Thread listenerThread = new Thread(new ParameterizedThreadStart(Listener));
        listenerThread.IsBackground = true;
        listenerThread.Start(form1);

        // Run the form
        System.Windows.Forms.Application.Run(form1);
    }

    static void Listener(object formObject)
    {
        Form1 form = (Form1)formObject;

        // Do whatever we need to do
        while (true)
        {
            Thread.Sleep(1000);
            form.AddLineToTextBox("Hello");
        }
    }
}

在这种情况下,Form1显然是表单类,并且Listener是监听方法。这里的关键是我将表单对象作为参数传递给 Listen 方法(通过 Thread.Start),以便侦听器可以访问 GUI 的非静态成员。请注意,我已将 Form1.AddLineToTextBox 定义为:

public void AddLineToTextBox(string line)
{
    if (textBox1.InvokeRequired)
        textBox1.Invoke(new Action(() => { textBox1.Text += line + Environment.NewLine; }));
    else
        textBox1.Text += line + Environment.NewLine;
}

请特别注意,由于现在该Listener方法在单独的线程中运行,因此您需要使用InvokeGUI 控件上的方法进行更改。我在这里使用了一个 lambda 表达式,但是如果你的目标是 .net 的早期版本,你可以很容易地使用一个完整的方法。请注意,我的 textBox1 是一个TextBox设置Multiline为 true 并ReadOnly设置为 false 的(类似于标签)。

另一种可能需要更多工作但可能更优雅的架构是执行相反的依赖关系:您使用对 Listener对象的引用创建表单。侦听器随后将引发 GUI 订阅的事件,以更新其显示。

于 2012-10-29T19:18:37.213 回答