web-dev-qa-db-fra.com

Utilisation de Application.DoEvents ()

Application.DoEvents() peut-il être utilisé en C #?

Cette fonction est-elle un moyen de permettre à l'interface graphique de rattraper le reste de l'application, à peu près de la même manière que le DoEvents de VB6

245
Craig Johnston

D'après mon expérience, je recommanderais une grande prudence lors de l'utilisation de DoEvents dans .NET. J'ai eu des résultats très étranges lorsque j'utilisais DoEvents dans un TabControl contenant DataGridViews. D'un autre côté, si tout ce que vous avez à faire est un petit formulaire avec une barre de progression, alors ça pourrait être OK.

L'essentiel est le suivant: si vous allez utiliser DoEvents, vous devez le tester minutieusement avant de déployer votre application.

13
Craig Johnston

Hmya, la mystique qui perdure dans DoEvents (). Il y a eu énormément de réactions négatives à son encontre, mais personne n'a jamais vraiment expliqué pourquoi c'est "mauvais". Le même genre de sagesse que "ne mute pas une structure". Euh, pourquoi le moteur d'exécution et le langage supportent-ils la mutation d'une structure si c'est si grave? Même raison: vous vous tirez une balle dans le pied si vous ne le faites pas correctement. Facilement. Et le faire correctement nécessite de savoir exactement ce qu'il fait, ce qui dans le cas de DoEvents () n'est certainement pas facile à maîtriser.

D'entrée de jeu, presque tous les programmes Windows Forms contiennent un appel à DoEvents (). Il est habilement déguisé, mais sous un nom différent: ShowDialog (). C'est DoEvents () qui permet à une boîte de dialogue d'être modale sans geler le reste des fenêtres de l'application.

La plupart des programmeurs souhaitent utiliser DoEvents pour empêcher leur interface utilisateur de geler lorsqu'ils écrivent leur propre boucle modale. Il fait certainement cela; il distribue des messages Windows et obtient toutes les demandes Paint livrées. Le problème est cependant que ce n'est pas sélectif. Il ne distribue pas que des messages Paint, il fournit également tout le reste.

Et il y a un ensemble de notifications qui posent problème. Ils viennent d'environ 3 pieds devant le moniteur. L'utilisateur peut par exemple fermer la fenêtre principale pendant que la boucle qui appelle DoEvents () est en cours d'exécution. Cela fonctionne, l'interface utilisateur est parti. Mais votre code ne s’est pas arrêté, il exécute toujours la boucle. C'est mauvais. Très très mauvais.

Il y a plus: l'utilisateur peut cliquer sur le même élément de menu ou sur le même bouton pour lancer la même boucle. Vous avez maintenant deux boucles imbriquées exécutant DoEvents (), la boucle précédente est suspendue et la nouvelle boucle commence à partir de zéro. Cela pourrait marcher, mais les chances sont minces. En particulier lorsque la boucle imbriquée se termine et que la boucle suspendue reprend, en essayant de terminer un travail déjà terminé. Si cela ne bombarde pas avec une exception, alors les données sont sûrement brouillées.

Retour à ShowDialog (). Il exécute DoEvents (), mais notez qu'il fait autre chose. Il désactive toutes les fenêtres de l'application , autres que la boîte de dialogue. Maintenant que le problème des 3 pieds est résolu, l'utilisateur ne peut rien faire pour gâcher la logique. Les modes d'échec fermer la fenêtre et recommencer le travail sont à nouveau résolus. Autrement dit, l'utilisateur n'a aucun moyen de faire exécuter le code du programme dans un ordre différent. Il s'exécutera de manière prévisible, exactement comme lorsque vous avez testé votre code. Cela rend les dialogues extrêmement ennuyeux; qui ne déteste pas avoir un dialogue actif et ne pas être capable de copier et coller quelque chose d'une autre fenêtre? Mais c'est le prix.

C’est ce qu’il faut pour utiliser DoEvents en toute sécurité dans votre code. La définition de la propriété Enabled de tous vos formulaires sur false est un moyen rapide et efficace d'éviter les problèmes. Bien sûr, aucun programmeur n'a jamais vraiment aimé faire cela. Et pas. C'est pourquoi vous ne devriez pas utiliser DoEvents (). Vous devriez utiliser des discussions. Même s'ils vous offrent un arsenal complet de façons de tirer votre pied de manière colorée et impénétrable. Mais avec l'avantage que vous ne tirez que sur votre propre pied; il ne laissera pas (généralement) l'utilisateur tirer le sien.

Les prochaines versions de C # et VB.NET fourniront un pistolet différent avec les nouveaux mots-clés wait et async. Inspiré en partie par les problèmes causés par DoEvents et les threads, mais en grande partie par la conception de l'API de WinRT qui nécessite de maintenir votre interface utilisateur à jour pendant le déroulement d'une opération asynchrone. Comme lire dans un fichier.

444
Hans Passant

Cela peut être, mais c'est un bidouillage.

Voir DoEvents Is Evil?.

Directement depuis la page MSDN that thedev reference:

L'appel de cette méthode provoque le courant le fil doit être suspendu alors que tout les messages de la fenêtre en attente sont traités . Si un message provoque un événement d'être déclenché, puis d'autres zones de votre le code de l'application peut s'exécuter. Cela peut faire exposer votre application comportements inattendus qui sont difficile à déboguer. Si vous effectuez opérations ou calculs qui prennent un longtemps, il est souvent préférable de effectuer ces opérations sur un nouveau fichier fil. Pour plus d'informations sur programmation asynchrone, voir Vue d'ensemble de la programmation asynchrone.

Microsoft met donc en garde contre son utilisation.

De plus, je le considère comme un piratage, car son comportement est imprévisible et ses effets secondaires prédisposés (cela vient de l'expérience d'essayer d'utiliser DoEvents au lieu de créer un nouveau fil ou d'utiliser un arrière-plan).

Il n'y a pas de machisme ici - si cela fonctionnait comme une solution robuste, je serais partout. Cependant, essayer d’utiliser DoEvents dans .NET ne m’a causé que de la douleur.

27
RQDQ

Oui, il existe une méthode DoEvents statique dans la classe Application de l'espace de noms System.Windows.Forms. System.Windows.Forms.Application.DoEvents () peut être utilisé pour traiter les messages en attente dans la file d'attente du thread d'interface utilisateur lors de l'exécution d'une tâche de longue durée dans le thread d'interface utilisateur. Cela présente l'avantage de rendre l'interface utilisateur plus réactive et non "bloquée" pendant l'exécution d'une longue tâche. Toutefois, il s’agit presque toujours PAS de la meilleure façon de procéder . Selon Microsoft, appelant DoEvents "... provoque l’interruption du thread en cours pendant le traitement de tous les messages de la fenêtre en attente." Si un événement est déclenché, il est possible que des bogues inattendus et intermittents soient difficiles à détecter. Si vous avez une tâche importante, il est de loin préférable de le faire dans un thread séparé. L'exécution de tâches longues dans un thread distinct leur permet d'être traitées sans interférer avec le fonctionnement continu de l'interface utilisateur. Regardez ici pour plus de détails.

Here est un exemple d'utilisation de DoEvents; notez que Microsoft met également en garde contre son utilisation.

23
Bill W

Oui.

Toutefois, si vous devez utiliser Application.DoEvents, il s'agit généralement d'une mauvaise conception d'application. Peut-être aimeriez-vous travailler à la place dans un fil séparé?

10

J'ai vu de nombreuses applications commerciales, en utilisant le "DoEvents-Hack". Surtout quand le rendu entre en jeu, je vois souvent ceci:

while(running)
{
    Render();
    Application.DoEvents();
}

Ils connaissent tous le mal de cette méthode. Cependant, ils utilisent le hack, car ils ne connaissent aucune autre solution. Voici quelques approches tirées d'un blog post by Tom Miller :

  • Définissez votre formulaire pour que tous les dessins soient réalisés dans WmPaint et y effectuent le rendu. Avant la fin de la méthode OnPaint, assurez-vous de procéder à this.Invalidate (); La méthode OnPaint sera de nouveau déclenchée immédiatement.
  • P/Invoke dans l'API Win32 et appelez PeekMessage/TranslateMessage/DispatchMessage. (Doevents fait quelque chose de similaire, mais vous pouvez le faire sans les allocations supplémentaires).
  • Ecrivez votre propre classe de formulaires qui forme une petite enveloppe autour de CreateWindowEx et donnez-vous le contrôle total sur la boucle de messages . -Décidez que la méthode DoEvents fonctionne bien pour vous et respectez-la.
4
Matthias

Consultez la documentation MSDN pour la méthode Application.DoEvents .

3
thedev

J'ai vu le commentaire de jheriko ci-dessus et je pensais au début que je ne pouvais pas éviter d'utiliser DoEvents si vous finissiez par faire tourner votre thread principal d'interface utilisateur en attendant qu'un morceau de code long et asynchrone se termine. Mais de la réponse de Matthias, un simple rafraîchissement d'un petit panneau sur mon interface utilisateur peut remplacer les événements (et éviter un effet secondaire désagréable).

Plus de détails sur mon cas ...

Je faisais ce qui suit (comme suggéré ici ) pour m'assurer qu'un écran de démarrage de type barre de progression ( Comment afficher une superposition "de chargement" ... ) mis à jour au cours d'une longue commande SQL:

IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery();
while (!asyncResult.IsCompleted)  //UI thread needs to Wait for Async SQL command to return
{
      System.Threading.Thread.Sleep(10); 
      Application.DoEvents();  //to make the UI responsive
}

The bad: Pour moi, appeler DoEvents signifiait que des clics de souris se déclenchaient parfois sur des formulaires derrière mon écran de démarrage, même si je l'avais rendu TopMost.

The good/answer: Remplacez la ligne DoEvents par un simple appel de rafraîchissement à un petit panneau situé au centre de l'écran de démarrage, FormSplash.Panel1.Refresh(). L'interface utilisateur est mise à jour correctement et l'étrangeté DoEvents que d'autres ont prévenue avait disparu.

3
TamW

Application.DoEvents peut créer des problèmes si un élément autre que le traitement graphique est placé dans la file d'attente des messages. 

Cela peut être utile pour mettre à jour les barres de progression et informer l'utilisateur de l'avancement de la construction et du chargement de MainForm, si cela prend un certain temps.

Dans une application récente que j'ai faite, j'ai utilisé DoEvents pour mettre à jour certaines étiquettes sur un écran de chargement chaque fois qu'un bloc de code est exécuté dans le constructeur de mon MainForm. Le thread d'interface utilisateur était, dans ce cas, occupé à envoyer un courrier électronique sur un serveur SMTP qui ne supportait pas les appels SendAsync (). J'aurais probablement pu créer un thread différent avec les méthodes Begin () et End () et appeler Send () à partir de leur méthode, mais cette méthode est sujette aux erreurs et je préférerais que la forme principale de mon application ne lève pas d'exceptions pendant la construction.

2
Guest123

DoEvents permet à l'utilisateur de cliquer ou de taper et de déclencher d'autres événements, et les threads d'arrière-plan constituent une meilleure approche.

Cependant, il existe encore des cas où vous pouvez rencontrer des problèmes nécessitant le vidage des messages d'événement. J'ai rencontré un problème où le contrôle RichTextBox ignorait la méthode ScrollToCaret () lorsque le contrôle avait des messages en file d'attente à traiter.

Le code suivant bloque toutes les entrées utilisateur lors de l'exécution de DoEvents:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}
0