views:

2092

answers:

5

strong textI 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.  ");
            Thead.Sleep(1000);
        }
    }
}
+8  A: 

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.  ");
            Thead.Sleep(1000);
        }
    }

}
bendewey
I tried your code, and it's not quite working for me. I appreciate the answer mate.
More specifically, it compiles, but the TextBox isn't continuously being written to.
Can you post the code your trying?
bendewey
Added the full sample
bendewey
In your code you took out the while(true) put that back into your sample function and you should be rocking. Thats gonna write a lot though. You might want to put a Thread.Sleep(1000) for 1 sec if your trying to test responsiveness
bendewey
Bendewey, you rock mate! I probably spent 2 hours banging my head against my monitor.
A: 

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.

Dan C.
+1  A: 

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 );
        }
    }
}
+1  A: 

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

Chris KL
A: 

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