Запись в TextBox из другого потока? - PullRequest
56 голосов
/ 06 февраля 2009

Я не могу понять, как заставить приложение Windows C Form писать в текстовое поле из потока. Например, в 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. ";
}

Я совершенно не прав?

UPDATE

Вот пример рабочего кода, предоставленного 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 ]

57 голосов
/ 06 февраля 2009

На вашей 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);
        }
    }
}
13 голосов
/ 11 декабря 2010

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

13 голосов
/ 06 февраля 2009

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

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 );
        }
    }
}
5 голосов
/ 06 февраля 2009

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

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

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);
  }
}
3 голосов
/ 13 августа 2014

Самый простой, без заботы о делегатах

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

else
    textBox1.Text = "Invoke was NOT needed"; 
3 голосов
/ 06 февраля 2009

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

2 голосов
/ 22 мая 2017

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

Вот моя Button.Click функция - я хочу сгенерировать случайное число потоков и затем получить их IDs, вызвав метод 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; 
    });
}
1 голос
/ 06 февраля 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...