【问题标题】:Update progress bar in another form while task is running任务运行时以另一种形式更新进度条
【发布时间】:2015-04-15 00:17:49
【问题描述】:

**最终我将有四个任务同时运行,并有另一个包含四个进度条的表单。我希望每个进度条在其工作任务完成时更新。

这就是我想要为初学者做的事情。

我有一个表单,上面有一些按钮。当我单击一个时,我正在创建一个新任务来做一些工作。

public partial class MyMainForm : Form
{

    private void btn_doWork_Click(object sender, EventArgs e)
    {
        Task task = new Task(RunComparisons);
        task.Start();
    }

    private void RunComparisons()
    {
        int progressBarValue = 0;
        MyProgressBarForm pBar = new MyProgressBarForm(maxValue, "some text");
        pBar.ShowDialog();
        foreach(string s in nodeCollection)
        {
            //do some work here
            progressBarValue++;
            pBar.updateProgressBar(progressBarValue, "some new text");
        }
        pBar.BeginInvoke(new Action(() => pBar.Close()));
    }
}

在另一个包含带有进度条的表单的类中:

public partial class MyProgressBarForm : Form
{
    public MyProgressBarForm(int maxValue, string textToDisplay)
    {
        InitializeComponent();
        MyProgressBarControl.Maximum = maxValue;
        myLabel.Text = textToDisplay;
    }

    public void updateProgressBar(int progress, string updatedTextToDisplay)
    {
        MyProgressBarForm.BeginInvoke(
            new Action(() =>
            {
                MyProgressBarControl.Value = progress;
                myLabel.Text = updatedTextToDisplay;
            }));
    }

当我单击 doWork 按钮时,进度条表单会显示但不会更新。它只是坐在那里挂起。如果我注释掉 pBar.ShowDialog();那么进度条表单不显示,但要完成的工作完美地运行完成。

当我创建自己的线程时,我可以完美地运行它,但我阅读了有关 Tasks 的信息,现在我正试图让它以这种方式运行。我哪里做错了?

【问题讨论】:

  • 不要在后台任务/线程中创建表单/控件。如果你这样做,那么你必须为这些线程创建一个消息泵。
  • @EZI,你有这样的例子吗?
  • 你考虑过使用BackgroundWorker吗?这似乎是一个很好的用例

标签: c# winforms c#-5.0


【解决方案1】:

TPL 添加了IProgress 接口,用于根据长时间运行的非 UI 操作的进度更新 UI。

您需要做的就是在您的 UI 中创建一个 Progress 实例,其中包含有关如何使用进度更新它的说明,然后将其传递给您的工作人员,该工作人员可以通过它报告进度。

public partial class MyMainForm : System.Windows.Forms.Form
{
    private async void btn_doWork_Click(object sender, EventArgs e)
    {
        MyProgressBarForm progressForm = new MyProgressBarForm();
        progressForm.Show();
        Progress<string> progress = new Progress<string>();
        progress.ProgressChanged += (_, text) =>
            progressForm.updateProgressBar(text);
        await Task.Run(() => RunComparisons(progress));
        progressForm.Close();
    }
    private void RunComparisons(IProgress<string> progress)
    {
        foreach (var s in nodeCollection)
        {
            Process(s);
            progress.Report("hello world");
        }
    }
}
public partial class MyProgressBarForm : System.Windows.Forms.Form
{
    public void updateProgressBar(string updatedTextToDisplay)
    {
        MyProgressBarControl.Value++;
        myLabel.Text = updatedTextToDisplay;
    }
}

这让进度表单处理向 UI 显示进度,工作代码只处理工作,主表单简单地创建进度表单,开始工作,完成后关闭表单,它留下所有通过 UI 线程跟踪进度和编组到Progress 的工作。它还避免了多个 UI 线程;您当前从非 UI 线程创建和操作 UI 组件的方法会产生许多问题,使代码复杂化并使其更难维护。

【讨论】:

  • 不知道TPLProgress 的选项。这应该是被接受的答案。
  • 如何将progressBar 最大值动态传递给MyProgressBarForm?我尝试在 MyProgressBarForm 中创建一个方法,在该方法中从 Form1 调用以设置 progressBar 最大值。当我这样做时,我收到有关另一个线程访问表单错误的错误。我知道是什么原因造成的,但我不知道解决方法。任何想法将不胜感激。
【解决方案2】:

在父窗体的主 UI 线程上创建进度条窗体,然后在按钮单击事件中调用对象的 Show() 方法。

这是一个有 2 个小节的示例:

//In parent form ...
private MyProgressBarForm progressBarForm = new MyProgressBarForm();

private void button1_Click(object sender, EventArgs e)
{
    progressBarForm.Show();
    Task task = new Task(RunComparisons);
    task.Start();
}

private void RunComparisons()
{
    for (int i = 1; i < 100; i++)
    {
        System.Threading.Thread.Sleep(50);
        progressBarForm.UpdateProgressBar(1, i);
    }
}

//In MyProgressBarForm ...
public void UpdateProgressBar(int index, int value)
{
    this.Invoke((MethodInvoker) delegate{
        if (index == 1)
        {
            progressBar1.Value = value;
        }
        else
        {
            progressBar2.Value = value;
        }
    });
}

【讨论】:

  • 只有一个问题 - 进度条的 maxValue 是可变的。可能是 5 或 200。如果我声明 progressbarForm 而不将最大值传递给构造函数,我该如何设置最大值?
  • @UmarSlobberknocker 有很多方法可以做到这一点。一种方法是在 MyProgressBarForm 中创建一个新方法,为给定的进度条设置一个新的最大值,然后在开始更新之前调用它(如果您不在 UI 线程上执行此操作,则需要再次调用)。
  • 知道了!效果很好!非常感谢!
  • 问题。当我从我创建的线程运行比较功能时,我的程序运行得非常快,但现在我将它作为一项任务运行,它需要永远完成。一项任务比跑步慢得多吗?
  • @UmarSlobberknocker 见this question。另外,你真的应该为此提出一个新问题。
【解决方案3】:

.ShowDialog 是阻塞调用;在对话框返回结果之前不会继续执行。您可能应该查看BackgroundWorker 来处理另一个线程上的工作并更新对话框。

【讨论】:

  • BackGroundWorker 是否会在任务中完成工作时工作?
  • @UmarSlobberknocker 我认为他的意思是放弃任务方法并使用 BackgroundWorker 的 DoWork 方法进行工作
  • 我会在后台工作人员而不是任务中完成工作。使用 Task 不会自动使您的代码异步。
  • 嗯,最终我将有超过 4 个我希望同时运行的任务。这就是我问的原因。我将有一个包含四个进度条的表单,并希望在每个工作任务完成时更新每个进度条。
  • 使用 BGW 会后退一大步,它会使代码比实际需要的要臃肿得多。 TPL 是一个显着的改进。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-20
  • 2023-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多