progress bar background worker

aronmatthew

Active member
Joined
Aug 5, 2019
Messages
41
Programming Experience
Beginner
I'm having a problem with a from constructor that in some cases is taking a while to put together. Here is the block of code that is causing a delay.
delay block:
  foreach(string file in overwriteList)
  {
      fileCheckBox = new CheckBox();
      fileCheckBox.Left = selectAllButton.Left+5;
      fileCheckBox.AutoSize = true;
      if (previous == null)
          fileCheckBox.Top = selectAllButton.Bottom;
      else
          fileCheckBox.Top = previous.Bottom;

      fileCheckBox.Tag = file;
      this.Controls.Add(fileCheckBox);

      fileLabel = new Label();
      fileLabel.Top = fileCheckBox.Top;
      fileLabel.Left = fileCheckBox.Right + 5;
      fileLabel.Text = file;
      fileLabel.AutoSize = true;
      this.Controls.Add(fileLabel);

      if(fileCheckBox.Width+fileLabel.Width>largeWidth)
      largeWidth = fileCheckBox.Width + fileLabel.Width;


      previous = fileCheckBox;
      index++;
  }
I would like to show a progress bar while this block of code is loading within a form constructor. I not sure where to place the background worker to use with a progress bar in this case.
 
I was finally able to crock some thing up with static references that threw a cross thread exception from that block of code that I opted to place within background worker of progress bar do work method. Where then I had to add a try catch block that seems to have solved it. But for some reason it seems like its not the right way to handle this case.
 
Consider using IProgress, here is a conceptual example.

Basic example:
namespace IProgressApp;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Shown += Form1_Shown;
    }

    private async void Form1_Shown(object? sender, EventArgs e)
    {
        var progressIndicator = new Progress<int>(ReportProgress);
        try
        {
            await PerformWork(progressIndicator);
        }
        catch (Exception exception)
        {
            // TODO
        }
    }

    private async Task PerformWork(IProgress<int> progress)
    {
        int index = 0;
        int top = 50;

        for (; ; )
        {
            if (index == 100)
            {
                await Task.Delay(100); // Simulate some work
                Controls.Add(new Label() { Parent = this, Text ="Done", Top = top });
                progress?.Report(index);
                break;
            }

            Controls.Add(new Label() { Parent = this, Text = index.ToString(), Top = top });
            progress?.Report(index);

            index += 10;
            top += 20;
        }
    }

    private void ReportProgress(int currentValue)
    {
        progressBar1.Value = currentValue;
    }
}
 
I was finally able to crock some thing up with static references that threw a cross thread exception from that block of code that I opted to place within background worker of progress bar do work method. Where then I had to add a try catch block that seems to have solved it. But for some reason it seems like its not the right way to handle this case.

That's correct. The background worker, by its nature, runs on a background thread. For the sake of sanity, WinForms doesn't allow cross thread messages (even though you can often get away with it if you are using the Win32 APIs directly). So if you want to update the UI from the worker thread, you need to use Invoke(). The IProgress noted by @kareninstructor abstracts away that bit.
 
The background worker, by its nature, runs on a background thread. [...] So if you want to update the UI from the worker thread, you need to use Invoke().

The BackgroundWorker exists specifically so you don't have to do that. The point is that you use methods and events in exactly the same way as with controls, so there's no need to understand invoking delegates. You're supposed to call ReportProgress in the DoWork event handler, which is executed on a worker thread, and then handle the ProgressChanged event and update the UI in the ProgressChanged event handler, which is executed on the UI thread.
 
Thanks for the correction! I've my used the class in years.
 
To just add a bit to jmc's post

All one has to do in a BgW to update a UI is:

* Set BgW's WorkerReportsProgress property to true
* Attach an event handler to the ProgressChanged and do all of your UI control access in that event handler
* Call one of the BgW's ReportProgress method overloads

ReportProgress takes an int progress and optionally a state object. You can use the object state or the int progress to communicate different stages to the ProgressChanged event - int percentage doesn't have to be a number out of 100 nor does it have to incrèment each time ReportProgress is called. You can thus use it to signal states and use a switch/if in the ProgressChanged to interpret the state
 
Back
Top Bottom