Writing to a TextBox from another thread?

Refresh

December 2018

Views

129.3k time

52

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

10 answers

3

Что еще проще, чтобы просто использовать элемент управления BackgroundWorker ...

5

Вы должны выполнить действие из потока, которому принадлежит контроль.

Вот как я делаю, что без добавления слишком много коды шума:

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

Где Invoke перегрузки простое расширение от Lokad общих библиотек :

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

Вот что я сделал, чтобы избежать CrossThreadException и запись в текстовое поле из другого потока.

Вот моя кнопка мыши функции- Я хочу, чтобы сгенерировать случайное число потоков, а затем получить их идентификаторы, вызвав GetId (); Способ и значение TextBox в то же время в этом потоке.

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

или вы можете сделать, как

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

или вы можете сделать, как

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

Посмотрите Control.BeginInvoke метод. Дело в том , чтобы никогда не обновлять элементы управления пользовательского интерфейса из другого потока. BeginInvoke направит вызов в UI потоке контроля (в вашем случае, форма).

Для того, чтобы захватить форму, удалить статический модификатор из функции выборки и использовать this.BeginInvoke (), как показано в примерах из MSDN.

3

Самое простое, не заботясь о делегатов

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

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

На вашем 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, если это необходимо.

Полный образец

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

Я хотел бы использовать BeginInvokeвместо Invokeтак часто , как это возможно, если вы действительно должны ждать , пока ваш контроль не был обновлен (что в вашем примере это не так). BeginInvokeдолжности делегата на очереди WinForms сообщений и позволяет вызывающий код действуйте немедленно (в вашем случае для цикла в SampleFunction). 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 , чтобы быть известным. Вот небольшой учебник по SynchronizationContext.

12

или вы можете сделать, как

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