从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
只需使用以下内容:
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
其他回答
只需使用以下内容:
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
必须使用invoke和delegate
private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
基本上,无论框架版本或GUI底层库类型如何,解决此问题的方法都是保存控件,为工作线程创建线程的同步上下文,从而将控件的相关交互从工作线程编组到GUI的线程消息队列。
例子:
SynchronizationContext ctx = SynchronizationContext.Current; // From control
ctx.Send\Post... // From worker thread
当我遇到同样的问题时,我向谷歌寻求帮助,但它并没有给我一个简单的解决方案,而是举了MethodInvoker和blah blah的例子,让我更加困惑。所以我决定自己解决。以下是我的解决方案:
制作一个这样的代理:
Public delegate void LabelDelegate(string s);
void Updatelabel(string text)
{
if (label.InvokeRequired)
{
LabelDelegate LDEL = new LabelDelegate(Updatelabel);
label.Invoke(LDEL, text);
}
else
label.Text = text
}
您可以像这样在新线程中调用此函数
Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();
不要与线程(()=>…..)混淆。在处理线程时,我使用匿名函数或lambda表达式。为了减少代码行,您也可以使用ThreadStart(..)方法,我不打算在这里解释。
对于.NET 2.0,下面是我编写的一段代码,它完全符合您的需要,适用于控件上的任何属性:
private delegate void SetControlPropertyThreadSafeDelegate(
Control control,
string propertyName,
object propertyValue);
public static void SetControlPropertyThreadSafe(
Control control,
string propertyName,
object propertyValue)
{
if (control.InvokeRequired)
{
control.Invoke(new SetControlPropertyThreadSafeDelegate
(SetControlPropertyThreadSafe),
new object[] { control, propertyName, propertyValue });
}
else
{
control.GetType().InvokeMember(
propertyName,
BindingFlags.SetProperty,
null,
control,
new object[] { propertyValue });
}
}
这样称呼:
// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
如果您使用的是.NET 3.0或更高版本,可以将上述方法重写为Control类的扩展方法,这样可以简化对以下内容的调用:
myLabel.SetPropertyThreadSafe("Text", status);
2010年10月5日更新:
对于.NET 3.0,应使用以下代码:
private delegate void SetPropertyThreadSafeDelegate<TResult>(
Control @this,
Expression<Func<TResult>> property,
TResult value);
public static void SetPropertyThreadSafe<TResult>(
this Control @this,
Expression<Func<TResult>> property,
TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member
as PropertyInfo;
if (propertyInfo == null ||
!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
@this.GetType().GetProperty(
propertyInfo.Name,
propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TResult>
(SetPropertyThreadSafe),
new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(
propertyInfo.Name,
BindingFlags.SetProperty,
null,
@this,
new object[] { value });
}
}
它使用LINQ和lambda表达式来允许更干净、更简单和更安全的语法:
// status has to be of type string or this will fail to compile
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status);
现在不仅在编译时检查了属性名称,而且属性的类型也是如此,因此不可能(例如)将字符串值分配给布尔属性,从而导致运行时异常。
不幸的是,这并不能阻止任何人做一些愚蠢的事情,例如传递另一个控件的属性和值,因此以下内容将很好地编译:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
因此,我添加了运行时检查,以确保传入的属性实际上属于调用该方法的控件。虽然不完美,但仍比.NET2.0版本好得多。
如果有人对如何改进此代码以确保编译时安全有任何进一步的建议,请发表评论!