问题的名称是:" 从后台工作者更新GUI ",但正确的名称世界是:" 从更新GUI后台工作者或报告来自后台工作人员的多变量(除了整数)"
请让我解释一下我的情况。在一个程序中,我有一个后台工作者来分析信息。作为这种分析的结果 - 形式GUI元素应该填充必要的数据。在GUI中我想更新
据我所知 - 我只能通过后台工作人员int
方法本地报告1 ReportProgress()
值。
所以问题是 - 如何通过List<>
传递string
(+其他一些变量:int
,ReportProgress()
)?基本上 - 我想用信息更新GUI但是&#34; 1整数&#34;只是不会这样做..所以要么可以通过ReportProgress()
传递多个变量,或者我可以使用BackgroundWorker内部的Invoke
来更新GUI。我个人不喜欢#39; t喜欢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() + ".");
}
答案 0 :(得分:6)
调用UserState
时有一个ReportProgress
参数。
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
事件,并且从那里一次更新你的用户界面。
答案 1 :(得分:4)
我编写了两个非常简单的方法,可以让您调用代码(仅在需要时),并且只需要编写一次代码。我认为这使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;
(只会改变调试模式btw中的行为)并查看是否遇到问题。
通常情况下,你实际上并没有。我花了很长时间才找到案例非常需要Invoke才能让事情变得混乱。
答案 2 :(得分:0)
从另一个线程更新UI的基本模式是:
If controlItem.InvokeRequired Then
controlItem.Invoke(Sub() controlItem.Text = textUpdateValue)
Else
controlItem.Text = textUpdateValue
End If
这可以更新您的控件列表,而无需通过ReportProgress传递任何内容。如果您想从线程内更新控件,我认为您不需要检查InvokeRequired,因为它始终是必需的。但是,最佳实践可能是通过属性公开控件的设置,然后进行全面检查,以便您可以从任何地方调用它。