119

I want to show progress of calculations, which are performing in external library.

For example if I have some calculate method, and I want to use it for 100000 values in my Form class I can write:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }            

    private void Caluculate(int i)
    {
        double pow = Math.Pow(i, i);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        progressBar1.Maximum = 100000;
        progressBar1.Step = 1;

        for(int j = 0; j < 100000; j++)
        {
            Caluculate(j);
            progressBar1.PerformStep();
        }
    }
}

I should perform step after each calculation. But what if I perform all 100000 calculations in external method. When should I "perform step" if I don't want to make this method dependant on progress bar? I can, for example, write

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void CaluculateAll(System.Windows.Forms.ProgressBar progressBar)
    {
        progressBar.Maximum = 100000;
        progressBar.Step = 1;

        for(int j = 0; j < 100000; j++)
        {
            double pow = Math.Pow(j, j); //Calculation
            progressBar.PerformStep();
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        CaluculateAll(progressBar1);
    }
}

but I don't want to do like that.

David Hall
  • 32,624
  • 10
  • 90
  • 127
Dmytro
  • 16,668
  • 27
  • 80
  • 130

4 Answers4

123

I would suggest you have a look at BackgroundWorker. If you have a loop that large in your WinForm it will block and your app will look like it has hanged.

Look at BackgroundWorker.ReportProgress() to see how to report progress back to the UI thread.

For example:

private void Calculate(int i)
{
    double pow = Math.Pow(i, i);
}

private void button1_Click(object sender, EventArgs e)
{
    progressBar1.Maximum = 100;
    progressBar1.Step = 1;
    progressBar1.Value = 0;
    backgroundWorker.RunWorkerAsync();
}

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    var backgroundWorker = sender as BackgroundWorker;
    for (int j = 0; j < 100000; j++)
    {
        Calculate(j);
        backgroundWorker.ReportProgress((j * 100) / 100000);
    }
}

private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressBar1.Value = e.ProgressPercentage;
}

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // TODO: do something with final calculation.
}
Quan
  • 473
  • 7
  • 16
Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
  • 9
    Nice example, but there's a small error in your code. You need to set the backgroundWorker.WorkerReportsProgress to true. Check my edit – Mana May 09 '17 at 08:02
  • 3
    @mana The assumption is that the `BackgroundWorker` is added via the designer and configured there. But yes, it will need to be configured to have `WorkerReportsProgress` set to `true`. – Peter Ritchie May 10 '17 at 00:40
  • ah, my bad, did not know that you could set it in the designer – Mana May 10 '17 at 08:11
  • Wondering something else though, your example only counts to 99 backgroundWorker.ReportProgress((j * 100) / 100000); how to get 100 % count – Mana May 10 '17 at 08:12
  • 2
    @mana If you want to show the 100% in the progress, do it in the `RunWorkerCompleted` event handler, if your `DoWork` handler doesn't do it.. – Peter Ritchie May 10 '17 at 15:18
  • actually, i came up with a fix. After the loop, i report 100, which gives me the 100 % status – Mana May 11 '17 at 11:03
  • I had a problem with threading and must include: progressBar1.BeginInvoke(new Action(() => { progressBar1.Value = e.ProgressPercentage; })); in the backgroundWorker_ProgressChanged method – Anders Finn Jørgensen Jun 11 '18 at 12:48
94

Since .NET 4.5 you can use combination of async and await with Progress for sending updates to UI thread:

private void Calculate(int i)
{
    double pow = Math.Pow(i, i);
}

public void DoWork(IProgress<int> progress)
{
    // This method is executed in the context of
    // another thread (different than the main UI thread),
    // so use only thread-safe code
    for (int j = 0; j < 100000; j++)
    {
        Calculate(j);

        // Use progress to notify UI thread that progress has
        // changed
        if (progress != null)
            progress.Report((j + 1) * 100 / 100000);
    }
}

private async void button1_Click(object sender, EventArgs e)
{
    progressBar1.Maximum = 100;
    progressBar1.Step = 1;

    var progress = new Progress<int>(v =>
    {
        // This lambda is executed in context of UI thread,
        // so it can safely update form controls
        progressBar1.Value = v;
    });

    // Run operation in another thread
    await Task.Run(() => DoWork(progress));

    // TODO: Do something after all calculations
}

Tasks are currently the preferred way to implement what BackgroundWorker does.

Tasks and Progress are explained in more detail here:

LolPython
  • 168
  • 1
  • 11
quasoft
  • 5,291
  • 1
  • 33
  • 37
  • 3
    This should be the selected answer, IMO. Great answer! – JohnOpincar Feb 08 '17 at 22:09
  • Almost almost the nicest answer, except that it is using Task.Run instead of a normal async function – KansaiRobot Jul 19 '18 at 08:49
  • @KansaiRobot You mean as opposed to `await DoWorkAsync(progress);`? This is very much on purpose, as that would not result in an extra thread running. Only if `DoWorkAsync` would call it's own `await` for e.g. waiting on an I/O operation, would the `button1_Click` function continue. The main UI thread is blocked for this duration. If `DoWorkAsync` is not really async but just a lot of synchronous statements, you don't gain anything. – Wolfzoon Aug 01 '18 at 10:27
  • System.Windows.Controls.ProgressBar doesn't contain contain a field "Step". It should be removed from the example; especially since it isn't used anyway. – Robert Tausig Nov 19 '18 at 09:46
  • 2
    @RobertTausig It's true that `Step` is available only in WinForms' progress bar and is not needed here, but It was present in the question's example code (tagged winforms), so may be it could stay. – quasoft Nov 19 '18 at 18:44
  • very nice answer and very sound responses to the comments! Thank YOU! – Teo Oct 23 '19 at 07:22
  • `Progress` is unnesscery, using `Invoke` instead. – Moon soon Feb 19 '20 at 09:57
  • I still get the error "Cross-thread operation not valid: Control 'progressBar1' accessed from a thread other than the thread it was created on" and copied the exact snipped. Do I need to set something else for the progressBar1 ? – Patrick Dec 21 '22 at 20:04
  • I found this method used about twice as much RAM compared to BackgroundWorker. Also my "DoWork" code usually hung the second time of calling, so I have reverted to using BackgroundWorker. – Skyfish Feb 06 '23 at 11:11
4

Hey there's a useful tutorial on Dot Net pearls: http://www.dotnetperls.com/progressbar

In agreement with Peter, you need to use some amount of threading or the program will just hang, somewhat defeating the purpose.

Example that uses ProgressBar and BackgroundWorker: C#

using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Start the BackgroundWorker.
            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 1; i <= 100; i++)
            {
                // Wait 100 milliseconds.
                Thread.Sleep(100);
                // Report progress.
                backgroundWorker1.ReportProgress(i);
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Change the value of the ProgressBar to the BackgroundWorker progress.
            progressBar1.Value = e.ProgressPercentage;
            // Set the text.
            this.Text = e.ProgressPercentage.ToString();
        }
    }
} //closing here
Deniz
  • 429
  • 1
  • 4
  • 19
Chong Ching
  • 425
  • 3
  • 7
2

There is Task exists, It is unnesscery using BackgroundWorker, Task is more simple. for example:

ProgressDialog.cs:

   public partial class ProgressDialog : Form
    {
        public System.Windows.Forms.ProgressBar Progressbar { get { return this.progressBar1; } }

        public ProgressDialog()
        {
            InitializeComponent();
        }

        public void RunAsync(Action action)
        {
            Task.Run(action);
        }
    }

Done! Then you can reuse ProgressDialog anywhere:

var progressDialog = new ProgressDialog();
progressDialog.Progressbar.Value = 0;
progressDialog.Progressbar.Maximum = 100;

progressDialog.RunAsync(() =>
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(1000)
        this.progressDialog.Progressbar.BeginInvoke((MethodInvoker)(() => {
            this.progressDialog.Progressbar.Value += 1;
        }));
    }
});

progressDialog.ShowDialog();
Moon soon
  • 2,616
  • 2
  • 30
  • 51