C# GUI类如何从工作类接收定期更新?

3
我正在尝试将工作代码与GUI代码分离到完全不同的类中,但我希望能够向我的GUI报告进度更新和文件输出。例如,我希望我的GUI对工作类说:“从串口读取十行,但在每次接收时向我报告你所读取的内容”。目前,我最好的方法是让GUI循环十次,并在每个循环中向工作类发送一个命令来读取一件事并返回它。
我真的希望保持所有循环都在我的工作类的一侧,因为它将拥有更多信息可用(实际数据量将是可变的,工作类已经可以访问可用数据量,我希望不需要将其发送回GUI类来运行循环)。
我已经研究了BackgroundWorker,但它似乎只在长时间操作期间报告完成百分比,而没有其他内容,所以这对我没有什么帮助。有人有好主意如何实现这一点吗?
以下是一个程序的框架,希望能更好地说明我想做什么。你会如何编辑代码以满足我的要求?
GUI的主类:
class Main_Class
{
    ...
    /*  Assume in the area we have instantiated these items and placed them on the form:
    *   Button  DoSomething:  A button to do something
    *   TextBox ShowInfo:  A text box to report something from the worker class     
    */

    Worker_Class timewaster = new Worker_Class();

    private void buttonDoSomething_Click(object sender, EventArgs e)
    {
        timewaster.a_lengthy_task();
    }
}

单独的工作类:

class Worker_Class
{
    ...//Various Setup stuff up here

    void a_lengthy task()
    {
        int iteration = 0;

        while(iteration < 10)
        {
            Datetime saveNOW = Datetime.Now;        //lets say I report this back to the the GUI to write in that ShowInfo box
            Thread.sleep(10000);                    //To waste time and make this lengthy

            //Your code here to facilitate sending saveNOW back to the the Main_Class and display it on the ShowInfo textbox.

            iteration++
        }
    }
}

这一切都与工人类有关! - user541686
4个回答

3
您需要的是事件。如果您无法通过内置于BackgroundWorker类中的内容使其为您工作,那么您至少可以按照它的做法来设计您的解决方案。
worker类应该有一个公共事件IterationComplete或者您想称呼的其他名称。您可以给它一个EventArgs对象(或者扩展EventArgs的对象),其中包含与UI相关的迭代信息。在每次迭代完成后(或者在需要进行UI修改的任何时候),都可以触发此事件。
然后UI可以订阅事件以处理与该迭代相关的所有UI任务。
代码示例:
主类:
public class MainClass
{
    Worker_Class timewaster = new Worker_Class();


    private void buttonDoSomething_Click(object sender, EventArgs e)
    {
        timewaster.IterationComplete += new EventHandler<Worker_Class.IterationEventArgs>(timewaster_IterationComplete);
        timewaster.LengthyTask();
    }

    void timewaster_IterationComplete(object sender, Worker_Class.IterationEventArgs e)
    {
        string infoFromWorker = e.iterationNumber;
    }
}

工作者类:

public class Worker_Class
{
    //Various Setup stuff up here

    public class IterationEventArgs : EventArgs
    {
        public string iterationNumber { get; set; }
    }

    public event EventHandler<IterationEventArgs> IterationComplete;

    public void LengthyTask()
    {
        int iteration = 0;

        while (iteration < 10)
        {
            DateTime saveNOW = DateTime.Now;        //lets say I report this back to the the GUI to write in that ShowInfo box
            Thread.Sleep(10000);                    //To waste time and make this lengthy

            //Your code here to facilitate sending saveNOW back to the the Main_Class and display it on the ShowInfo textbox.

            if (IterationComplete != null)
            {
                IterationEventArgs args = new IterationEventArgs();
                args.iterationNumber = iteration.ToString();
                IterationComplete(this, args);
            }

            iteration++;
        }
    }
}

非常感谢您,Servy。这正是我所需要的。我看到其他一些问题谈论使用事件来处理这个问题,但我目前对它们知之甚少,因此无法从其他问题中适应它们到我的代码中。您的回答恰到好处,既专门针对我需要的内容(并给出了我理解的上下文),又足够通用以帮助我学习如何使用它们,并完成工作。您有什么特别推荐的教程可以让我更多地了解C#中的事件处理吗? - Xantham

1

这是BackgroundWorker类的理想任务。假设您没有使用紧凑框架,它应该可供您使用。

BackgroundWorker上,您可以监听onCompletedonProgressUpdated事件。这些事件会为您编排到UI线程中,您可以安全地使用它们更新您的winform/wpf控件。要显示进度,您需要在BackgroundWorkerDoWork()中调用ReportProgress()

将代码带入单独的线程时,更新GUI控件时需要小心。因为更新控件的线程必须是UI线程。BackgroundWorker会为您处理此问题。

MSDN - http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx


正如OP所提到的,他需要传递给UI的不仅仅是ReportProgress传递的单个整数。 - Servy
抱歉,我没有注意到第三段。 - JonWillis

1

您需要让Worker_Class通过事件广播其进度。 Main_Class将订阅此事件并使用信息相应地更新UI。 从这里可以分为两个方向。 您可以使用Control.Invoke将此事件处理程序调度到UI线程,然后可以安全地更新GUI。 或者您可以让事件处理程序将进度信息保存到共享变量中。 然后,通过System.Windows.Forms.TimerDispatcherTimer在方便的时间间隔内轮询UI以获取此变量。 在这种情况下,我更喜欢后一种方法。

class YourForm : Form
{
  private volatile MyEventArgs progress = null;

  private void buttonDoSomething_Click(object sender, EventArgs args)
  {
    var timewaster = new Worker_Class();
    timewaster.ProgressChanged += (sender, args) { progress = args; };
    Task.Factory.StartNew(
      () =>
      {
        timewaster.a_lengthy_task();
      }
    UpdateTimer.Enabled = true;
  }

  private void UpdateTimer_Tick(object sender, EventArgs args)
  {
    if (progress != null)
    {
      if (progress.IsFinished)
      {
        ShowInfo.Text = "Finished";
        UpdateTimer.Enabled = false;
      }
      else
      {
        ShowInfo.Text = progress.SaveNow.Value.ToString();
      }
    }
    else
    {
      ShowInfo.Text = "No progress yet";
    }
  }
}

class Worker_Class
{
  public event EventHandler<MyEventArgs> ProgressChanged;

  public Worker_Class()
  {
    ProgressChanged += (sender, args) => { };
  }

  public void a_lengthy task()
  {
    int iteration = 0;
    while(iteration < 10)
    {
        Datetime saveNOW = Datetime.Now;
        Thread.sleep(10000);
        ProgressChanged(this, new MyEventArgs { SaveNow = saveNOW, IsFinished = false });
        iteration++
    }
    ProgressChanged(this, new MyEventArgs { SaveNow = null, IsFinished = true });
  }
}

class MyEventArgs : EventArgs
{
  public DateTime? SaveNow { get; set; }
  public bool IsFinished { get; set; }
}

0
在我的后台工作器中,我使用:
this.Invoke((MethodInvoker)delegate
{
    //do something in GUI
});

例如,如果你想在 GUI 中改变一个标签,请使用:
label.Text = "whatever";

在方法内部。


这可以“工作”(如果您将UI类的实例传递给工作者),但它并没有将UI代码与工作者代码分离,而是将所有代码移动到工作者类中。 - Servy
我明白你的意思。谢谢澄清。 - Ryan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接