Writing to a TextBox from another thread?

Viewed 179776

I cannot figure out how to make a C# Windows Form application write to a textbox from a thread. For example in the Program.cs we have the standard main() that draws the form:

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

Then we have in the Form1.cs:

public Form1()
{
    InitializeComponent();

    new Thread(SampleFunction).Start();
}

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

Am I going about this completely wrong?

UPDATE

Here is the working code sample provided from 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 Answers

On your MainForm make a function to set the textbox the checks the InvokeRequired

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

although in your static method you can't just call.

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

you have to have a static reference to the Form1 somewhere, but this isn't really recommended or necessary, can you just make your SampleFunction not static if so then you can just call

AppendTextBox("hi. ");

It will append on a differnt thread and get marshalled to the UI using the Invoke call if required.

Full Sample

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);
        }
    }
}

I would use BeginInvoke instead of Invoke as often as possible, unless you are really required to wait until your control has been updated (which in your example is not the case). BeginInvoke posts the delegate on the WinForms message queue and lets the calling code proceed immediately (in your case the for-loop in the SampleFunction). Invoke not only posts the delegate, but also waits until it has been completed.

So in the method AppendTextBox from your example you would replace Invoke with BeginInvoke like that:

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

Well and if you want to get even more fancy, there is also the SynchronizationContext class, which lets you basically do the same as Control.Invoke/Control.BeginInvoke, but with the advantage of not needing a WinForms control reference to be known. Here is a small tutorial on SynchronizationContext.

or you can do like

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 );
        }
    }
}

You need to perform the action from the thread that owns the control.

That's how I'm doing that without adding too much code noise:

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

Where Invoke overload is a simple extension from 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);
  }
}

Have a look at Control.BeginInvoke method. The point is to never update UI controls from another thread. BeginInvoke will dispatch the call to the UI thread of the control (in your case, the Form).

To grab the form, remove the static modifier from the sample function and use this.BeginInvoke() as shown in the examples from MSDN.

What's even easier is to just use the BackgroundWorker control...

Related