从另一个线程写入TextBox?

时间:2009-02-06 05:39:13

标签: c# winforms multithreading

我无法弄清楚如何使C#Windows窗体应用程序从线程写入文本框。例如,在Program.cs中,我们有标准的main()绘制形式:

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
}

然后我们在Form1.cs中:

public Form1()
{
    InitializeComponent();

    new Thread(SampleFunction).Start();
}

public static void SampleFunction()
{
    while(true)
        WindowsFormsApplication1.Form1.ActiveForm.Text += "hi. ";
}

我是否完全错了?

更新

以下是bendewey提供的工作代码示例:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread(SampleFunction).Start();
    }

    public void AppendTextBox(string value)
    {
        if (InvokeRequired)
        {
            this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
            return;
        }
        textBox1.Text += value;
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for(int i = 0; i<5; i++)
        {
            AppendTextBox("hi.  ");
            Thread.Sleep(1000);
        }
    }
}

8 个答案:

答案 0 :(得分:57)

在MainForm上创建一个函数来设置文本框,检查InvokeRequired

public void AppendTextBox(string value)
{
    if (InvokeRequired)
    {
        this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
        return;
    }
    ActiveForm.Text += value;
}

虽然在静态方法中你不能只是打电话。

WindowsFormsApplication1.Form1.AppendTextBox("hi. ");

你必须在某个地方有一个对Form1的静态引用,但这不是真正推荐或必要的,你是否只是让你的SampleFunction不是静态的,那么你可以调用

AppendTextBox("hi. ");

如果需要,它将附加在不同的线程上并使用Invoke调用编组到UI。

完整样本

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread(SampleFunction).Start();
    }

    public void AppendTextBox(string value)
    {
        if (InvokeRequired)
        {
            this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
            return;
        }
        textBox1.Text += value;
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for(int i = 0; i<5; i++)
        {
            AppendTextBox("hi.  ");
            Thread.Sleep(1000);
        }
    }
}

答案 1 :(得分:13)

或者你可以这样做

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread( SampleFunction ).Start();
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for ( int i = 0; i < 5; i++ )
        {
            this.Invoke( ( MethodInvoker )delegate()
            {
                textBox1.Text += "hi";
            } );
            Thread.Sleep( 1000 );
        }
    }
}

答案 2 :(得分:13)

我会尽可能频繁地使用BeginInvoke代替Invoke,除非你真的需要等到你的控件更新后(在你的例子中并非如此)。 BeginInvoke在WinForms消息队列中发布委托,并让调用代码立即继续(在您的情况下是SampleFunction中的for循环)。 Invoke不仅会发布代理,还会等到代理完成。

因此,在您的示例的方法AppendTextBox中,您可以将Invoke替换为BeginInvoke,如下所示:

public void AppendTextBox(string value)
{
    if (InvokeRequired)
    {
        this.BeginInvoke(new Action<string>(AppendTextBox), new object[] {value});
        return;
    }
    textBox1.Text += value;
}

好吧,如果你想得到更多的幻想,还有SynchronizationContext类,它让你基本上和Control.Invoke/Control.BeginInvoke一样,但是不需要WinForms控件参考的优点要知道。 Here是关于SynchronizationContext的小型教程。

答案 3 :(得分:5)

您需要从拥有该控件的线程执行操作。

这就是我在不增加太多代码噪音的情况下这样做的原因:

control.Invoke(() => textBox1.Text += "hi");

Invoke重载是Lokad Shared Libraries的简单扩展名:

/// <summary>
/// Invokes the specified <paramref name="action"/> on the thread that owns     
/// the <paramref name="control"/>.</summary>
/// <typeparam name="TControl">type of the control to work with</typeparam>
/// <param name="control">The control to execute action against.</param>
/// <param name="action">The action to on the thread of the control.</param>
public static void Invoke<TControl>(this TControl control, Action action) 
  where TControl : Control
{
  if (!control.InvokeRequired)
  {
    action();
  }
  else
  {
    control.Invoke(action);
  }
}

答案 4 :(得分:3)

更简单的方法是使用BackgroundWorker控件...

答案 5 :(得分:3)

最简单,不关心代表

if(textBox1.InvokeRequired == true)
    textBox1.Invoke((MethodInvoker)delegate { textBox1.Text = "Invoke was needed";});

else
    textBox1.Text = "Invoke was NOT needed"; 

答案 6 :(得分:2)

以下是我为避免CrossThreadException并从另一个帖子写入文本框而采取的措施。

这是我的Button.Click函数 - 我想生成一个随机数的线程,然后通过在该工作线程中调用IDs方法和TextBox值来获取它们的getID()

private void btnAppend_Click(object sender, EventArgs e) 
{
    Random n = new Random();

    for (int i = 0; i < n.Next(1,5); i++)
    {
        label2.Text = "UI Id" + ((Thread.CurrentThread.ManagedThreadId).ToString());
        Thread t = new Thread(getId);
        t.Start();
    }
}

这是getId(workerThread)代码:

public void getId()
{
    int id = Thread.CurrentThread.ManagedThreadId;
    //Note that, I have collected threadId just before calling this.Invoke
    //method else it would be same as of UI thread inside the below code block 
    this.Invoke((MethodInvoker)delegate ()
    {
        inpTxt.Text += "My id is" +"--"+id+Environment.NewLine; 
    });
}

答案 7 :(得分:1)

查看Control.BeginInvoke方法。关键是永远不要从另一个线程更新UI控件。 BeginInvoke会将调用分派给控件的UI线程(在您的情况下为Form)。

要获取表单,请从示例函数中删除static修饰符,并使用this.BeginInvoke(),如MSDN中的示例所示。