web-dev-qa-db-fra.com

Écrire dans une TextBox à partir d'un autre thread?

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

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

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

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.

13
Mike

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

Ce qui est encore plus facile, c'est simplement d'utiliser le contrôle BackgroundWorker ...

3
Chris KL

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"; 
3
marsh-wiggle

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

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.

1
Dan C.