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


Dans WinForms, comment puis-je forcer une mise à jour immédiate de l'interface utilisateur à partir du thread d'interface utilisateur?

Ce que je fais est en gros:

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 défini sur "Please Wait ..." avant l'opération.

J'ai résolu ce problème en utilisant un autre thread pour l'opération, mais ça devient très difficile et je voudrais simplifier le code.


65
2017-09-01 07:00


origine


Réponses:


Au début, je me suis demandé pourquoi le programme opérationnel n’avait pas déjà identifié une des réponses comme réponse, mais après l’avoir essayé moi-même et toujours ne pas avoir fonctionné, j’ai creusé un peu plus et je me suis rendu compte que supposé.

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

Enfin, pour mémoire, 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 de ce que je comprends, c'est loin d'être une approche élégante et correcte pour le faire. C'est un piratage qui peut ou peut ne pas fonctionner selon le niveau d'activité du thread.


87
2018-04-02 19:25



Appel Application.DoEvents() après avoir défini le label, vous devez plutôt faire tout le travail dans un thread séparé, afin que l'utilisateur puisse fermer la fenêtre.


12
2017-09-01 07:07



Appel label.Invalidate et alors label.Update() - En général, la mise à jour ne se produit que lorsque vous quittez la fonction en cours mais l'appel à Update le force à se mettre à jour à cet endroit spécifique du code. De MSDN:

La méthode Invalidate régit ce qui est peint ou repeint. La méthode de mise à jour régit la peinture ou la peinture. Si vous utilisez les méthodes d'invalidation et de mise à jour ensemble plutôt que d'appeler Refresh, les éléments repeints dépendent de la surcharge d'invalidation que vous utilisez. La méthode de mise à jour force simplement le contrôle à être peint immédiatement, mais la méthode Invalidate régit ce qui est peint lorsque vous appelez la méthode Update.


12
2017-09-01 07:07



Je viens de tomber sur le même problème et j'ai trouvé des informations intéressantes et je voulais mettre mes deux centimes 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 agent d'arrière-plan, un thread explicite, un thread du pool de threads ou (depuis .Net 4.0) une tâche: Stackoverflow 570537: mise à jour-label-while-processing-in-windows-forms, afin que l'interface utilisateur reste réactif.

Mais pour les tâches courtes, il n'y a pas vraiment besoin de filetage, bien que cela ne nuise pas, 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 passait en revue le code (en utilisant F10) et voyais 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 en bas de la première page que le thread d'interface utilisateur ne peut pas repeindre l'interface utilisateur jusqu'à ce que la fonction actuellement exécutée se termine et que la fenêtre soit marquée par "non-réponse" au bout d'un certain temps. J'ai également remarqué cela sur mon application de test en le parcourant, mais seulement dans certains cas.

(Pour le test suivant, il est important de ne pas configurer Visual Studio en plein écran, vous devez pouvoir voir votre petite fenêtre d’application en même temps, vous ne devez pas basculer entre la fenêtre Visual Studio pour le débogage et votre fenêtre d'application pour voir ce qui se passe. Démarrez l'application, définissez un point d'arrêt à label1->Text ..., placez la fenêtre d’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 placer les focales et activer la progression) et parcourez-le SANS déplacer la souris, le nouveau texte est défini et l'étiquette est mise à jour dans la fonction update (). Cela signifie que l'interface utilisateur est repeinte de manière évidente.

  2. Lorsque je passe la première ligne, déplacez la souris et cliquez quelque part, puis avancez, 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 reste là jusqu'à ce que la fonction button1_click () se termine. Au lieu de repeindre, la fenêtre est marquée comme "non réactive"! Cela n'aide pas non plus à ajouter this->Update(); pour mettre à jour le formulaire entier.

  3. Ajouter Application::DoEvents(); donne à l’UI 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 ou effectuer d'autres opérations sur l'interface utilisateur qui ne sont pas autorisées! Donc: Essayez d'éviter DoEvents ()!, mieux vaut utiliser le threading (ce qui, à mon avis, est assez simple dans .Net).
    Mais (@Jagd, 2 avril 10 à 19h25) vous pouvez omettre .refresh() et .invalidate().

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

<> blague> Conclusion: il suffit donc d’empêcher l’utilisateur d’utiliser la souris ;-) <> / joke>


4
2017-08-07 14:03



vous pouvez essayer ceci

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
2017-12-04 07:03



Après avoir mis à jour l'interface utilisateur, lancez une tâche à effectuer 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
2018-04-16 06:32



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


1
2017-09-01 07:08



Essayez d'appeler label.Invalidate ()

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invalidate(VS.80).aspx


1
2017-09-01 07:09



Pense que j'ai la réponse, distillée à partir de ce qui précède et une petite 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 le réglage progressBar.Value à progressBar.Maximum, parce que vous ne pouvez pas définir la valeur de la barre de progression au-dessus du maximum, donc je commence par définir le progressBar.Value à progressBar.Maximum -1, puis définir progressBar.Maxiumum égaler progressBar.Value. Ils disent qu'il y a plus d'une façon de tuer un chat. Parfois, je voudrais tuer Bill Gates ou quiconque est maintenant: o).

Avec ce résultat, je n'ai même pas eu besoin de Invalidate(), Refresh(), Update()ou faites quelque chose dans la barre de progression, son conteneur Panel ou le formulaire parent.


1
2017-10-04 15:18



J'ai eu le même problème avec la propriété Enabledet j'ai découvert un first chance exception élevé à cause de cela est pas thread-safe. J'ai trouvé une solution à propos de "Comment mettre à jour l'interface graphique d'un autre thread en C #?" ici https://stackoverflow.com/a/661706/1529139 Et il fonctionne !


0
2017-10-24 09:20