Update progress bar in another form while task is running

18,048

Solution 1

Create your progress bar form on the main UI thread of the parent form, then call the Show() method on the object in your button click event.

Here's an example with 2 bars:

//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;
        }
    });
}

Solution 2

The TPL adds the IProgress interface for updating the UI with the progress of a long running non-UI operation.

All you need to do is create a Progress instance in your UI with instructions on how to update it with progress, and then pass it to your worker which can report progress through it.

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;
    }
}

This lets the Progress Form handle displaying progress to the UI, the working code to only handle doing the work, the main form to simply create the progress form, start the work, and close the form when done, and it leaves all of the work of keeping track of progress and marhsaling through the UI thread to Progress. It also avoids having multiple UI thread; your current approach of creating and manipulating UI components from non-UI threads creates a number of problems that complicates the code and makes it harder to maintain.

Share:
18,048
UmarSlobberknocker
Author by

UmarSlobberknocker

Updated on June 14, 2022

Comments

  • UmarSlobberknocker
    UmarSlobberknocker almost 2 years

    **Ultimately I am going to have four tasks running concurrently and have another form that contains four progress bars. I would like for each progress bar to update as it's work task is completing.

    Here's what I'm trying to do for starters.

    I have a form that has some buttons on it. When I click one I'm creating a new task to do some work.

    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()));
        }
    }
    

    In another class that contains a form with a progress bar:

    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;
                }));
        }
    

    When I click the doWork button the progress bar form displays but doesn't update. It just sits there and hangs. If I comment out the pBar.ShowDialog(); then the progress bar form doesn't display but the work to be done is run to completion perfectly.

    I had this working perfectly when I was creating my own threads but I read about Tasks and now I'm trying to get this to run that way. Where did I go wrong?

  • UmarSlobberknocker
    UmarSlobberknocker over 9 years
    Will BackGroundWorker work while the work is being done in a task?
  • Setsu
    Setsu over 9 years
    @UmarSlobberknocker I think he means to just abandon the task approach and do your work on the BackgroundWorker's DoWork method
  • Bradley Uffner
    Bradley Uffner over 9 years
    I would do the work inside the background worker instead of a Task. Using Task doesn't automatically make your code asynchronous.
  • UmarSlobberknocker
    UmarSlobberknocker over 9 years
    Well, ultimately I'm going to have upwards of 4 tasks that I would like to all run concurrently. That's why I'm asking. I'll have a form with four progress bars and would like to get each progress bar to update as each work task is completing.
  • UmarSlobberknocker
    UmarSlobberknocker over 9 years
    Just one question - the maxValue of the progress bar will be variable. It could be 5 or 200. If I declare the progressbarForm without passing the max value to the constructor how do I set the max?
  • Setsu
    Setsu over 9 years
    @UmarSlobberknocker There are a number of ways to accomplish that. One way is to make a new method in MyProgressBarForm that sets a new max for a given progress bar, and then you call it before you start updating (If you're not doing this on the UI thread, you'd need to invoke again).
  • UmarSlobberknocker
    UmarSlobberknocker over 9 years
    Got it! Works great! Thank you so much!
  • Servy
    Servy over 9 years
    Using a BGW would be taking a big step back, it would make the code quite a bit more bloated than what it actually needs to be. The TPL is a noticeable improvement.
  • UmarSlobberknocker
    UmarSlobberknocker over 9 years
    question. When I was running my compare functionality from a thread that I created my program ran ridiculously fast, but now that I have it running as a task it takes forever to complete. Is a task that much slower than a tread?
  • Setsu
    Setsu about 9 years
    @UmarSlobberknocker See this question. Also, you should really start a new question for this.
  • Faizan Mubasher
    Faizan Mubasher over 6 years
    Didn't know that TPL has option of Progress. This should be accepted answer.
  • NuWin
    NuWin over 5 years
    How would I dynamically pass the progressBar maximum to the MyProgressBarForm? I've tried creating a method in MyProgressBarForm in which gets called from Form1 to set the progressBar maximum. When I do it, I get an error about another thread accessing the form error. I know what is causing it it but I dont know a work around. Any thoughts would be greatly appreciated it.