从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
前面回答中的Invoke内容都不是必需的。
您需要查看WindowsFormsSynchronizationContext:
// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();
...
// In some non-UI Thread
// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);
...
void UpdateGUI(object userData)
{
// Update your GUI controls here
}
其他回答
您必须确保更新发生在正确的线程上;UI线程。
为了做到这一点,您必须调用事件处理程序,而不是直接调用它。
您可以通过以下方式发起活动:
(代码是在我的脑海中打出来的,所以我没有检查语法等是否正确,但它应该能让你继续。)
if( MyEvent != null )
{
Delegate[] eventHandlers = MyEvent.GetInvocationList();
foreach( Delegate d in eventHandlers )
{
// Check whether the target of the delegate implements
// ISynchronizeInvoke (Winforms controls do), and see
// if a context-switch is required.
ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;
if( target != null && target.InvokeRequired )
{
target.Invoke (d, ... );
}
else
{
d.DynamicInvoke ( ... );
}
}
}
请注意,上面的代码在WPF项目上不起作用,因为WPF控件不实现ISynchronizeInvoke接口。
为了确保上面的代码适用于Windows窗体和WPF以及所有其他平台,您可以查看AsyncOperation、AsyncOperationManager和SynchronizationContext类。
为了以这种方式轻松地引发事件,我创建了一个扩展方法,它允许我通过调用以下命令来简化引发事件:
MyEvent.Raise(this, EventArgs.Empty);
当然,您也可以使用BackGroundWorker类,它将为您抽象这个问题。
致敬!在搜索了这个问题之后,我发现FrankG和Oregon Ghost的答案对我来说是最简单、最有用的;所以我不太确定结果如何。
我有一个名为form_Diagnostics的对话框,它有一个称为updateDiagWindow的富文本框,我正在使用它作为一种日志显示。我需要能够从所有线程更新它的文本。额外的行允许窗口自动滚动到最新的行。
因此,我现在可以从整个程序的任何地方,以您认为无需任何线程的方式,用一行代码更新显示:
form_Diagnostics.updateDiagWindow(whatmessage);
主代码(将其放入表单的类代码中):
#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
var _with1 = diagwindow;
if (_with1.InvokeRequired) {
_with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
} else {
UpdateDiag(whatmessage);
}
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
var _with2 = diagwindow;
_with2.appendtext(whatmessage);
_with2.SelectionStart = _with2.Text.Length;
_with2.ScrollToCaret();
}
#endregion
创建类变量:
SynchronizationContext _context;
在创建UI的构造函数中设置它:
var _context = SynchronizationContext.Current;
要更新标签时:
_context.Send(status =>{
// UPDATE LABEL
}, null);
Marc Gravell的.NET 4最简单解决方案的变体:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或者改用Action委托:
control.Invoke(new Action(() => control.Text = "new text"));
请参阅此处了解两者的比较:MethodInvoker与Action for Control.BeginInvoke
Label lblText; //initialized elsewhere
void AssignLabel(string text)
{
if (InvokeRequired)
{
BeginInvoke((Action<string>)AssignLabel, text);
return;
}
lblText.Text = text;
}
请注意,BeginInvoke()比Invoke(()更受欢迎,因为它不太可能导致死锁(然而,在这里,将文本分配给标签时,这不是问题):
使用Invoke()时,您正在等待方法返回。现在,可能是您在调用的代码中执行了一些需要等待线程的操作,如果它隐藏在您正在调用的某些函数中,这可能不会立即显现出来,而这本身可能会通过事件处理程序间接发生。因此,您将等待线程,线程将等待您,并且您处于死锁状态。
这实际上导致了我们发布的一些软件挂起。用BeginInvoke()替换Invoke(。除非需要同步操作(如果需要返回值,则可能是这种情况),否则请使用BeginInvoke()。