GUI update from background worker

Question title: "Updating the GUI from the desktop", but the correct world of names: "Updating the GUI from the desktop or reporting several variables (other than the whole) from the desktop"

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

  • 2 datagridviews
  • 1 listbox
  • 5 shortcuts

As I understand it - I can only state 1 int value through the ReportProgress() background worker method.

So, the question is how to pass List<> (+ some other variables: string , int ) via ReportProgress() ? Basically - I want to update the GUI with the help of information, but "1 integer" just will not. Therefore, you can either pass several variables through ReportProgress() OR I can use Invoke from inside BackgroundWorker to update the GUI myself .. Personally, I don’t like the Invoke approach ... What do you think?

Here is my code (see 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() + "."); } 
+6
source share
3 answers

When calling ReportProgress , the UserState parameter is UserState .

 var list_result = new List<List<string>>(); new backgroundWorker1.ReportProgress(0, list_result); 

The type of the parameter is object , so you need to return it back to the type you need:

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

The tricky issue is how you determine if you are passing a List or a list of lists, or a single line, number, etc. You will need to check every opportunity 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 can create a class that contains all the values ​​you need (or use Tuple ), run everything in the background to fill this class, and then pass this to the RunWorkerCompleted event and update your interface right away.

+6
source

I wrote two very simple methods that allow you to call your code (only if necessary), and you only need to write your code once. I think this makes Invoke much more convenient:

1) BeginInvoke

 public static void SafeBeginInvoke(System.Windows.Forms.Control control, System.Action action) { if (control.InvokeRequired) control.BeginInvoke(new System.Windows.Forms.MethodInvoker(() => { action(); })); else action(); } 

2) Call

 public static void SafeInvoke(System.Windows.Forms.Control control, System.Action action) { if (control.InvokeRequired) control.Invoke(new System.Windows.Forms.MethodInvoker(() => { action(); })); else action(); } 

It can be called like this:

 SafeInvoke(textbox, () => { textbox.Text = "text got changed"; }); 

Alternatively, you can simply

 System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false; 

(which only changes the behavior in btw debugging mode) and looks if you encounter problems.
More often than not, you don’t actually do this. It took me quite a while to find the cases in which Invoke really takes to keep things from messing around.

+4
source

The main template for updating the user interface from another thread:

 If controlItem.InvokeRequired Then controlItem.Invoke(Sub() controlItem.Text = textUpdateValue) Else controlItem.Text = textUpdateValue End If 

This can update the list of controls without having to pass anything through ReportProgress. If you want to update your control from a stream, I don’t think you need to check InvokeRequired because it will always be necessary. However, the best practice may be to expose the control setting through the property, and then perform a full check so that you can call it from anywhere.

0
source

Source: https://habr.com/ru/post/971286/


All Articles