从后台工作器更新GUI

本文关键字:更新 GUI 工作 后台 | 更新日期: 2023-09-27 18:10:08

问题的名称是:"从后台worker更新GUI ",但正确的名称应该是:"从后台worker更新GUI或从后台worker报告多个变量(整数以外)"

请让我解释我的情况。在一个程序中,我有一个后台工作人员分析信息。作为分析的结果,表单GUI元素应该填充必要的数据。在GUI中,我想更新

  • 2 datagridviews
  • 1列表框
  • <
  • 5标签/gh>

据我所知-我只能通过后台工作人员的ReportProgress()方法本地报告1 int

所以问题是-我如何通过ReportProgress()传递List<>(+一些其他变量:string, int) ?基本上-我想用信息更新GUI,但"1整数"只是不会做…因此,应该可以通过ReportProgress()传递多个变量,或者可以从BackgroundWorker本身内部使用Invoke来更新GUI。我个人不喜欢Invoke的方法…你怎么看?

下面是我的代码(见注释):

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

从后台工作器更新GUI

调用ReportProgress时有一个UserState参数

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

参数类型是一个object,所以你必须把它转换回你需要的类型:

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

这个棘手的问题是,你如何确定你是否传递回一个List,或列表的列表,或单个字符串,数字等。你必须在ProgressChanged事件中测试每种可能性。

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
}

或者,您可以创建一个类来保存您需要的所有值(或使用Tuple),在后台运行所有内容以填充该类,然后将其传递给RunWorkerCompleted事件,并从那里一次更新您的UI。

我已经编写了两个非常简单的方法,使您能够调用代码(仅在需要时),并且您只需要编写一次代码。我认为这使得Invoke使用起来更加友好:

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)调用

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

可以这样调用:

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

或者你可以直接

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

(仅在调试模式下更改行为),并查看是否遇到问题。
通常情况下,你实际上并没有。我花了很长时间才发现,在某些情况下,非常需要调用才能使事情不变得混乱。

从另一个线程更新UI的基本模式是:

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

这可以更新控件列表,而不需要通过ReportProgress传递任何内容。如果你想从线程内更新你的控件,我不相信你需要检查invokerrequiredd,因为它总是需要的。然而,最佳实践可能是通过属性公开控件的设置,然后进行完整检查,以便您可以从任何地方调用它。