从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
为了在WPF中实现这一点,我采用以下方式。
new Thread(() =>
{
while (...)
{
SomeLabel.Dispatcher.BeginInvoke((Action)(() => SomeLabel.Text = ...));
}
}).Start();
其他回答
出于许多目的,它非常简单:
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
“serviceGUI()”是表单(this)中的GUI级方法,可以根据需要更改任意数量的控件。从另一个线程调用“updateGUI()”。可以添加参数来传递值,或者(可能更快)根据需要使用带有锁的类范围变量,如果访问这些变量的线程之间可能发生冲突,从而导致不稳定。如果非GUI线程是时间关键的(请记住BrianGideon的警告),请使用BeginInvoke而不是Invoke。
线程代码经常有bug,而且总是很难测试。您不需要编写线程代码来从后台任务更新用户界面。只需使用BackgroundWorker类来运行任务及其ReportProgress方法来更新用户界面。通常,您只报告完成百分比,但还有一个重载包含状态对象。下面是一个仅报告字符串对象的示例:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "A");
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "B");
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "C");
}
private void backgroundWorker1_ProgressChanged(
object sender,
ProgressChangedEventArgs e)
{
label1.Text = e.UserState.ToString();
}
如果您总是想更新同一个字段,这很好。如果要进行更复杂的更新,可以定义一个类来表示UI状态,并将其传递给ReportProgress方法。
最后一件事,确保设置WorkerReportsProgress标志,否则ReportProgress方法将被完全忽略。
只使用ui的同步上下文
using System.Threading;
// ...
public partial class MyForm : Form
{
private readonly SynchronizationContext uiContext;
public MyForm()
{
InitializeComponent();
uiContext = SynchronizationContext.Current; // get ui thread context
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(() =>
{// set ui thread context to new thread context
// for operations with ui elements to be performed in proper thread
SynchronizationContext
.SetSynchronizationContext(uiContext);
label1.Text = "some text";
});
t.Start();
}
}
一般方法如下:
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
int clickCount = 0;
public Form1()
{
InitializeComponent();
label1.SetText("0");
}
private void button1_Click(object sender, EventArgs e)
{
new Thread(() => label1.SetText((++clickCount).ToString())).Start();
}
}
public static class ControlExtensions
{
public static void SetText(this Control control, string text)
{
if (control.InvokeRequired)
control.Invoke(setText, control, text);
else
control.Text = text;
}
private static readonly Action<Control, string> setText =
(control, text) => control.Text = text;
}
}
说明:
答案很像这个。但使用了更整洁(对我来说)和更新的语法。点是控件的InvokeRequired属性。它获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用调用方法,因为调用方所在的线程与创建控件的线程不同。因此,如果我们在创建控件的同一线程上调用control.SetText(“一些文本”),则只需将text设置为此控件即可。text=text。但在任何其他线程上,它都会导致System.InvalidOperationException,因此必须通过控件调用方法。Invoke(…)在创建的线程控件上设置Text。
您可以使用现有的委托操作:
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}