Je n'arrive pas à comprendre comment faire en sorte qu'une application Windows Form C # écrit dans une zone de texte à partir d'un thread. Par exemple, dans Program.cs, nous avons la norme main () qui dessine la forme:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
Ensuite, nous avons dans le Form1.cs:
public Form1()
{
InitializeComponent();
new Thread(SampleFunction).Start();
}
public static void SampleFunction()
{
while(true)
WindowsFormsApplication1.Form1.ActiveForm.Text += "hi. ";
}
Est-ce que je vais à ce sujet complètement faux?
MISE À JOUR
Voici l'exemple de code de travail fourni par 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);
}
}
}
Sur votre MainForm créer une fonction pour définir la zone de texte le vérifie la InvokeRequired
public void AppendTextBox(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
return;
}
ActiveForm.Text += value;
}
bien que dans votre méthode statique, vous ne pouvez pas simplement appeler.
WindowsFormsApplication1.Form1.AppendTextBox("hi. ");
vous devez avoir une référence statique au Form1 quelque part, mais ce n'est pas vraiment recommandé ou nécessaire, pouvez-vous simplement rendre votre SampleFunction non statique si oui, vous pouvez simplement appeler
AppendTextBox("hi. ");
Il sera ajouté à un thread différent et sera marshallé à l'interface utilisateur en utilisant l'appel Invoke si nécessaire.
Échantillon complet
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);
}
}
}
ou tu peux faire comme
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 );
}
}
}
J'utiliserais BeginInvoke
au lieu de Invoke
aussi souvent que possible, à moins que vous ne soyez vraiment obligé d'attendre que votre contrôle ait été mis à jour (ce qui dans votre exemple n'est pas le cas). BeginInvoke
publie le délégué dans la file d'attente des messages WinForms et laisse le code d'appel se poursuivre immédiatement (dans votre cas, la boucle for dans le SampleFunction
). Invoke
non seulement publie le délégué, mais attend également qu'il soit terminé.
Ainsi, dans la méthode AppendTextBox
de votre exemple, vous devriez remplacer Invoke
par BeginInvoke
comme ceci:
public void AppendTextBox(string value)
{
if (InvokeRequired)
{
this.BeginInvoke(new Action<string>(AppendTextBox), new object[] {value});
return;
}
textBox1.Text += value;
}
Eh bien, et si vous voulez avoir encore plus de fantaisie, il y a aussi la classe SynchronizationContext
, qui vous permet de faire la même chose que Control.Invoke/Control.BeginInvoke
, mais avec l’avantage de ne pas avoir besoin d’une référence de contrôle WinForms pour être connue. Ici est un petit tutoriel sur SynchronizationContext
.
Vous devez exécuter l'action à partir du thread propriétaire du contrôle.
C'est comme ça que je fais ça sans ajouter trop de bruit de code:
control.Invoke(() => textBox1.Text += "hi");
Où Invoke overload est une simple extension de 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);
}
}
Ce qui est encore plus facile, c'est simplement d'utiliser le contrôle BackgroundWorker ...
Le plus simple, sans se soucier des délégués
if(textBox1.InvokeRequired == true)
textBox1.Invoke((MethodInvoker)delegate { textBox1.Text = "Invoke was needed";});
else
textBox1.Text = "Invoke was NOT needed";
Voici ce que j'ai fait pour éviter CrossThreadException
et écrire dans la zone de texte à partir d'un autre thread.
Voici ma fonction Button.Click
- Je souhaite générer un nombre aléatoire de threads, puis obtenir leur IDs
en appelant la méthode getID()
et la valeur TextBox tout en étant dans cet utilisateur. fil.
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();
}
}
Voici le code 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;
});
}
Jetez un oeil à la méthode Control.BeginInvoke . Le but est de ne jamais mettre à jour les contrôles d'interface utilisateur depuis un autre thread. BeginInvoke enverra l'appel au thread d'interface utilisateur du contrôle (dans votre cas, le formulaire).
Pour récupérer le formulaire, supprimez le modificateur static de la fonction exemple et utilisez this.BeginInvoke (), comme indiqué dans les exemples fournis par MSDN.