从另一个线程更新标签的最简单方法是什么?

我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。

我怎么能这样做?


当前回答

例如,访问当前线程以外的控件:

Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));

lblThreshold是一个标签,Speed_Threshold则是一个全局变量。

其他回答

只使用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();
    }
}

在这个问题上,大多数其他答案对我来说都有点复杂(我是C#新手),所以我写了我的答案:

我有一个WPF应用程序,并定义了如下工作人员:

问题:

BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}

解决方案:

workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}

我还没有弄清楚上面这句话的意思,但它奏效了。

对于WinForms:

解决方案:

txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});

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

致敬!在搜索了这个问题之后,我发现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

我想添加一个警告,因为我注意到一些简单的解决方案忽略了InvokeRequired检查。

我注意到,如果您的代码在创建控件的窗口句柄之前(例如,在显示表单之前)执行,Invoke会抛出异常。因此,我建议在调用Invoke或BeginInvoke之前始终检查InvokeRequired。