web-dev-qa-db-fra.com

Forcer la mise à jour de l'interface graphique à partir du fil de l'interface utilisateur

Dans WinForms, comment forcer une mise à jour immédiate de l'interface utilisateur à partir du fil de l'interface utilisateur? 

Ce que je fais est à peu près:

label.Text = "Please Wait..."
try 
{
    SomewhatLongRunningOperation(); 
}
catch(Exception e)
{
    label.Text = "Error: " + e.Message;
    return;
}
label.Text = "Success!";

Le texte de l'étiquette n'est pas réglé sur "Veuillez patienter ..." avant l'opération.

J'ai résolu ce problème en utilisant un autre thread pour l'opération, mais cela devient poilu et j'aimerais simplifier le code.

67
dbkk

Au début, je me demandais pourquoi le programme opérationnel n'avait pas déjà indiqué l'une des réponses comme réponse, mais après l'avoir essayé moi-même et ne l'avoir toujours pas fonctionné, j'ai creusé un peu plus profondément et j'ai trouvé qu'il y avait beaucoup plus à dire que ce que je voulais d'abord. supposé. 

Une meilleure compréhension peut être obtenue en lisant une question similaire: Pourquoi ne pas contrôler le contrôle de la mise à jour/actualisation en cours de processus

Enfin, pour l'enregistrement, j'ai pu mettre mon label à jour en procédant comme suit:

private void SetStatus(string status) 
{
    lblStatus.Text = status;
    lblStatus.Invalidate();
    lblStatus.Update();
    lblStatus.Refresh();
    Application.DoEvents();
}

Bien que, d’après ce que je sache, c’est loin d’être une approche élégante et correcte. C'est un hack qui peut ou peut ne pas fonctionner en fonction de l'occupation du thread.

90
Jagd

Appelez label.Invalidate puis label.Update() - généralement, la mise à jour ne se produit que lorsque vous quittez la fonction en cours mais l'appel de Update force la mise à jour à cet endroit spécifique du code . De MSDN :

La méthode Invalidate régit ce qui est peint ou repeint. La méthode Update régit le moment où la peinture ou le repeinte a lieu. Si vous utilisez les méthodes Invalidate et Update ensemble plutôt que d'appeler Refresh, ce qui sera repeint dépend de la surcharge d'invalidate que vous utilisez. La méthode Update force simplement à peindre le contrôle immédiatement, mais la méthode Invalidate régit ce qui est peint lorsque vous appelez la méthode Update.

14
Dror Helper

Appelez Application.DoEvents() après avoir défini le libellé, mais vous devez effectuer tout le travail dans un thread séparé, afin que l'utilisateur puisse fermer la fenêtre.

13
Scoregraphic

Je viens de trébucher sur le même problème et de trouver des informations intéressantes. Je voulais mettre mes deux sous et les ajouter ici. 

Tout d'abord, comme d'autres l'ont déjà mentionné, les opérations de longue durée doivent être effectuées par un thread, qui peut être un travailleur en arrière-plan, un thread explicite, un thread du pool de threads ou (depuis .Net 4.0) une tâche: Stackoverflow 570537: update-label-while-processing-in-windows-forms , afin que l'interface utilisateur reste réactif. 

Mais pour les tâches courtes, le filetage n’est pas vraiment nécessaire, bien que cela ne fasse pas mal, bien sûr. 

J'ai créé un winform avec un bouton et une étiquette pour analyser ce problème:

System::Void button1_Click(System::Object^  sender, System::EventArgs^  e)
{
  label1->Text = "Start 1";
  label1->Update();
  System::Threading::Thread::Sleep(5000); // do other work
}

Mon analyse consistait à parcourir le code (en utilisant F10) et à voir ce qui se passait. Et après avoir lu cet article Multithreading dans WinForms j'ai trouvé quelque chose d'intéressant. L'article indique au bas de la première page que le thread d'interface utilisateur ne peut pas repeindre l'interface utilisateur tant que la fonction en cours d'exécution n'est pas terminée et que la fenêtre est marquée par Windows comme "ne répondant pas" à la place après un certain temps. Je l’ai aussi remarqué lors de mon test d’application d’essai, mais seulement dans certains cas. 

(Pour le test suivant, il est important de ne pas paramétrer Visual Studio en mode plein écran. Vous devez pouvoir afficher votre petite fenêtre d'application en même temps. Vous ne devez pas nécessairement basculer entre la fenêtre Visual Studio pour le débogage et votre Démarrez l’application, définissez un point d’arrêt sur label1->Text ..., placez la fenêtre de l’application à côté de la fenêtre VS et placez le curseur de la souris sur la fenêtre VS.) 

  1. Lorsque je clique une fois sur VS après le démarrage de l'application (pour y mettre le focus et activer le pas à pas) et que pas à travers SANS déplacer la souris, le nouveau texte est défini et le libellé est mis à jour dans la fonction update (). Cela signifie que l'interface utilisateur est repeinte de toute évidence.

  2. Lorsque je franchis la première ligne, puis que je déplace beaucoup la souris et que je clique quelque part, puis que je vais plus loin, le nouveau texte est probablement défini et la fonction update () est appelée, mais l'interface utilisateur n'est pas mise à jour/repeinte et l'ancien texte y reste jusqu'à la fin de la fonction button1_click (). Au lieu de repeindre, la fenêtre est marquée comme "non réactif"! Cela n'aide pas non plus d'ajouter this->Update(); pour mettre à jour le formulaire entier. 

  3. L'ajout de Application::DoEvents(); donne à l'interface utilisateur une chance de mettre à jour/repeindre. Quoi qu'il en soit, vous devez veiller à ce que l'utilisateur ne puisse pas appuyer sur les boutons ni effectuer d'autres opérations non autorisées sur l'interface utilisateur! Par conséquent: Essayez d'éviter DoEvents ()! , il vaut mieux utiliser threading (ce qui, à mon avis, est assez simple en .Net).
    Mais (@Jagd, le 2 avril 10 à 19:25), vous pouvez omettre .refresh() et .invalidate().

Mes explications sont les suivantes: AFAIK winform utilise toujours la fonction WINAPI. Également/ Un article MSDN sur la méthode System.Windows.Forms Control.Update fait référence à la fonction WINAPI WM_Paint. L'article MSDN sur WM_Paint indique dans sa première phrase que la commande WM_Paint n'est envoyée par le système que lorsque la file d'attente des messages est vide. Mais comme la file de messages est déjà remplie dans le 2e cas, elle n’est pas envoyée et donc l’étiquette et le formulaire de demande ne sont pas repeints.

<> blague> Conclusion: vous devez donc empêcher l'utilisateur d'utiliser la souris ;-) <>/blague>

4
Tobias Knauss

tu peux essayer ça

using System.Windows.Forms; // u need this to include.

MethodInvoker updateIt = delegate
                {
                    this.label1.Text = "Started...";
                };
this.label1.BeginInvoke(updateIt);

Voyez si cela fonctionne.

3
Rick2047

Après la mise à jour de l'interface utilisateur, démarrez une tâche à exécuter avec l'opération longue:

label.Text = "Please Wait...";

Task<string> task = Task<string>.Factory.StartNew(() =>
{
    try
    {
        SomewhatLongRunningOperation();
        return "Success!";
    }
    catch (Exception e)
    {
        return "Error: " + e.Message;
    }
});
Task UITask = task.ContinueWith((ret) =>
{
    label.Text = ret.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());

Cela fonctionne dans .NET 3.5 et versions ultérieures.

2
pixelgrease

Il est très tentant de vouloir "réparer" cela et imposer une mise à jour de l'interface utilisateur, mais la meilleure solution consiste à le faire sur un thread en arrière-plan et à ne pas lier le thread de l'interface utilisateur afin qu'il puisse toujours répondre aux événements.

1
Mike Hall
1
Chetan

Pense que j'ai la réponse, distillée de ce qui précède et un peu d'expérimentation.

progressBar.Value = progressBar.Maximum - 1;
progressBar.Maximum = progressBar.Value;

J'ai essayé de décrémenter la valeur et l'écran mis à jour même en mode débogage, mais cela ne fonctionnerait pas pour définir progressBar.Value sur progressBar.Maximum, car vous ne pouvez pas définir la valeur de la barre de progression au dessus du maximum progressBar.Value à progressBar.Maximum -1, puis définissez progressBar.Maxiumum sur progressBar.Value. Ils disent qu'il y a plus d'une façon de tuer un chat. Parfois, j'aimerais tuer Bill Gates ou qui que ce soit maintenant: o).

Avec ce résultat, je ne semblais même pas avoir besoin de Invalidate(), Refresh(), Update(), ou de modifier la barre de progression, son conteneur Panel ou le formulaire parent.

1
Graham Bennett

J'ai eu le même problème avec la propriété Enabled et j'ai découvert un first chance exception généré à cause de son statut not-thread-safe . J'ai trouvé une solution sur "Comment mettre à jour l'interface graphique à partir d'un autre thread en C #?" ici https://stackoverflow.com/a/661706/1529139 Et ça marche!

0
56ka

Si vous avez seulement besoin de mettre à jour quelques contrôles, il suffit de .update ().

btnMyButton.BackColor=Color.Green; // it eventually turned green, after a delay
btnMyButton.Update(); // after I added this, it turned green quickly
0
user3029478