Запись в TextBox из другого потока?
Я не могу понять, как сделать приложение С# Windows 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);
}
}
}
Ответы
Ответ 1
На вашем 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. ");
Он будет добавляться в поток differnt и будет подключен к пользовательскому интерфейсу с использованием вызова 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);
}
}
}
Ответ 2
или вы можете сделать, например
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 );
}
}
}
Ответ 3
Я использовал бы BeginInvoke
вместо Invoke
как можно чаще, если вам не нужно ждать, пока ваш элемент управления не будет обновлен (что в вашем примере не так). BeginInvoke
помещает делегат в очередь сообщений WinForms и позволяет немедленно вызвать вызывающий код (в вашем случае for-loop в 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
.
Ответ 4
Вам нужно выполнить действие из потока, которому принадлежит элемент управления.
Вот как я это делаю, не добавляя слишком много шума кода:
control.Invoke(() => textBox1.Text += "hi");
Где Invoke overload - это простое расширение от 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);
}
}
Ответ 5
Что еще проще - просто использовать элемент управления BackgroundWorker...
Ответ 6
Самый простой, не заботясь о делегатах
if(textBox1.InvokeRequired == true)
textBox1.Invoke((MethodInvoker)delegate { textBox1.Text = "Invoke was needed";});
else
textBox1.Text = "Invoke was NOT needed";
Ответ 7
Вот то, что я сделал, чтобы избежать CrossThreadException
и записи в текстовое поле из другого потока.
Вот мой Button.Click
function-. Я хочу создать случайное количество потоков, а затем получить их 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;
});
}
Ответ 8
Посмотрите Control.BeginInvoke. Дело в том, чтобы никогда не обновлять элементы управления пользовательского интерфейса из другого потока. BeginInvoke отправит вызов в поток пользовательского интерфейса элемента управления (в вашем случае, форма).
Чтобы захватить форму, удалите статический модификатор из функции sample и используйте this.BeginInvoke(), как показано в примерах из MSDN.