Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating the GUI from background worker

The name of the question is: "Updating the GUI from background worker", but the correct name world be: "Updating the GUI from background worker OR reporting multiple-variables (other than an integer) from background worker"

Please let me explain my situation. In a program I have a background worker which analyses the information.As the result of this analysis - form GUI elements should be populated with necessary data. In GUI I would like to update

  • 2 datagridviews
  • 1 listbox
  • 5 labels

As I understand - I can only natively report 1 int value via ReportProgress() method of background worker.

So the question is - how can I pass a List<> ( + some other variables: string, int) via ReportProgress()? Basically - i want to update the GUI with the information but "1 integer" just won't do.. So either it should be possible to pass multiple variables via an ReportProgress() OR i can use an Invoke from inside the BackgroundWorker itself to update the GUI.. Personally I don't like the Invoke approach... What's your opinion?

Here is my code (see the comments):

   private void button9_Click(object sender, EventArgs e) // start BW
    {
        bw.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
        bw.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);

        bw.WorkerReportsProgress = true;
        bw.WorkerSupportsCancellation = true;

        bw.RunWorkerAsync(10);
    }

    private void button10_Click(object sender, EventArgs e) // cancel BW
    {
        bw.CancelAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        int count = (int)e.Argument;
        for (int i = 1; i <= count; i++)
        {
            if (bw.CancellationPending)
            {
                e.Cancel = true;
                break;
            }

            List<List<string>> list_result = new List<List<string>>();
            list_result = Proccess();

            bw.ReportProgress(list_result.Count()); // right now I can only return a single INT

            /////////// UPDATE GUI //////////////
            // change datagridview 1 based on "list_result" values
            // change datagridview 2
            // change listbox
            // change label 1
            // change label ..          

            Thread.Sleep(20000);
        }

        MessageBox.Show("Complete!");
        e.Result = sum;
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        prog_count++;
        listBox1.Items.Add("Count: (" + prog_count.ToString() + "/20). Found: " + e.ProgressPercentage.ToString() + ".");
    }
like image 940
Alex Avatar asked Jun 24 '14 13:06

Alex


1 Answers

There's a UserState parameter when calling ReportProgress.

var list_result = new List<List<string>>();

new backgroundWorker1.ReportProgress(0, list_result);

The parameter type is an object so you'll have to cast it back to the type you need:

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    var userState = (List<List<string>>)e.UserState;
}   

The tricky issue with this is, how do you determine whether you're passing back a List, or a list of lists, or a single string, number, etc. You'll have to test for each possibility in the ProgressChanged event.

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    var myList = e.UserState as List<List<string>>;
    if (myList != null)
    {
        // use list
        return;
    }

    int myNumber;
    if (Int32.TryParse(e.UserState.ToString(), out myNumber))
    {
        // use number
        return;
    }

    var myString = e.UserState.ToString();
    // use string
}

Alternatively, you could create a class that holds all the values you need (or use Tuple), run everything in the background to populate that class, then pass that to the RunWorkerCompleted event, and update your UI all at once from there.

like image 124
Grant Winney Avatar answered Oct 28 '22 14:10

Grant Winney