【问题标题】:Updating the GUI from background worker从后台工作人员更新 GUI
【发布时间】:2014-08-14 18:39:15
【问题描述】:

问题的名称是:“从后台工作人员更新 GUI”,但正确的名称是:“从后台工作人员更新 GUI 或报告多变量(其他比整数)来自后台工作人员

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

  • 2 个数据网格视图
  • 1 个列表框
  • 5 个标签

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

所以问题是 - 我如何通过ReportProgress() 传递List<>(+ 一些其他变量:stringint)?基本上 - 我想用信息更新 GUI,但“1 个整数”是不行的。所以要么应该可以通过 ReportProgress() 传递多个变量,要么我可以在 BackgroundWorker 内部使用 Invoke自己更新 GUI。我个人不喜欢 Invoke 方法...你有什么意见?

这是我的代码(参见 cmets):

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

【问题讨论】:

  • 你不喜欢Invoke的什么地方?
  • ReportProgress 还有一个UserState 可以传递的对象。
  • @Savanna 并不是我讨厌它。我正在为我目前的解决方案寻找一个替代/更好的观点;)也许通过从 BW 转移到“新线程”将是一个解决方案.. 就我个人而言,我不喜欢调用,因为我必须检查“如果 InvokeRequired,那么……”但同样 - 如果没有其他选择,那么我将不得不坚持使用这个

标签: c# multithreading winforms backgroundworker


【解决方案1】:

调用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立即从那里开始。

【讨论】:

  • 您的第一个 sn-p 创建了一个名为 list_result 的变量,但是您将名为 myList 的未定义的东西传递给后台工作人员的 ReportProgress 方法。您的意思是通过list_result 还是离开线路?
  • 或者...我们可以使用“e.ProgressPercentage”来检测一种对象。如果 (e.ProgressPercentage = 1) => 对象是一个字符串。 "2" -> Obj 是一个 Int,所以做其他事情,"3" -> ...
【解决方案2】:

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

1) 开始调用

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;

(顺便说一句,它只会改变调试模式下的行为)并查看您是否遇到问题。
通常情况下您实际上没有。我花了很长时间才发现非常需要 Invoke 才能使事情不被搞砸的情况。

【解决方案3】:

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

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

这可以更新您的控件列表,而无需您通过 ReportProgress 传递任何内容。如果您想从线程中更新您的控件,我认为您不需要检查 InvokeRequired,因为它始终是必需的。但是,最佳做法可能是通过属性公开控件的设置,然后进行全面检查,以便您可以从任何地方调用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-24
    • 1970-01-01
    • 1970-01-01
    • 2013-02-27
    • 1970-01-01
    • 1970-01-01
    • 2019-08-09
    相关资源
    最近更新 更多