Ce tutoriel est une partie de : celui-ci.
La fonction DoEvents suspend l'exécution de la macro afin de rendre la main au système d'exploitation. Celui-ci peut alors traiter les messages en attente dans ses files d'attentes.
DoEvents retourne un Integer représentant le nombre de feuilles ouvertes dans les versions autonomes de Visual Basic (Visual Basic édition professionnelle, par exemple). Elle renvoie un zéro dans toutes les autres applications.
A chaque fois que l'on doit rendre la main au système pour qu'il exécute une éventuelle action de l'utilisateur (clic de souris, frappes clavier etc.).
Le DoEvents peut améliorer la vitesse d'exécution dans le cas d'échange d'informations entre le processeur et des périphériques qui lui sont associés. Si votre code attend une entrée d'un tel dispositif, DoEvents accélère l'application en « multitâches ». En conséquence, l'ordinateur ne semble pas suspendre ou cesser de répondre (se bloquer), tandis que le code s'exécute.
DoEvents, dans ce cas, permet à votre système de traiter d'autres informations, d'autres messages en attente. C'est le cas, par exemple, si vous devez attendre le rafraichissement d'une page Internet.
Do Until IE.ReadyState = READYSTATE_COMPLETE DoEvents Loop
Lorsque votre code génère un objet géré par un appel à votre système d'exploitation, il faut que cet objet soit totalement disponible pour pouvoir s'exécuter correctement. L'exemple sur le site de Microsoft démarre l'application Word grâce à la commande Shell. Si Word n'est pas encore ouvert, tout effort visant à établir un lien vers elle arrêtera le code. En utilisant DoEvents, votre procédure permet de s'assurer qu'une opération, telles que Shell, est complètement exécutée avant l'instruction de macro suivante est traitée.
z% = Shell("WinWord Source.Doc",1) DoEvents
On imagine ici l'appel d'une application qui nécessite une autre application pour obtenir des données. Si votre code ne laisse pas la main pour que se réalise cet échange entre les deux applications, le résultat sera erroné. DoEvents lors de la communication entre les deux applications permet d'éviter ce type de blocage.
Lorsque votre code affiche, dans une Form, par exemple, des informations les unes après les autres, si vous ne souhaitez pas voir le résultat qu'en fin de macro, il vous faudra y intégrer des DoEvents.
Voyez ce test avec un UserForm en VBA :
Private Sub CommandButton1_Click() TextBox1 = "Texte 1" DoEvents For i = 1 To 50000000: Next TextBox2 = "Texte 2" DoEvents For i = 1 To 50000000: Next TextBox3 = "Texte 3" End Sub
Faites ce test avec et sans les 2 DoEvents.
Utiliser trop d'instructions DoEvents imbriquées peut épuiser l'espace de pile et donc générer un message d'erreur « espace de pile insuffisant ». Cette erreur fait référence à l'espace de pile d'application attribué à la demande de l'application.
De la même manière, utiliser DoEvents en boucle ne peut que solliciter abusivement le processeur et en provoquer la surchauffe. Il ne s'agit d'augmentation de mémoire utilisée, mais de sollicitation abusive.
Lorsque vous rendez la main de manière temporaire à votre processeur dans une procédure d'événement, veillez à ce que la procédure ne soit pas exécutée à nouveau par une autre portion de votre programme, avant que le résultat du premier appel ne soit renvoyé. Cela peut provoquer des résultats imprévisibles.
Une fois que DoEvents cède le contrôle du système d'exploitation, il n'est pas possible de déterminer quand votre application va reprendre le contrôle. Après que le système d'exploitation prend le contrôle du processeur, il traite tous les événements en attente qui sont actuellement dans la file d'attente de messages (tels que les clics de souris et les frappes de clavier). Cela peut ne pas convenir pour certaines applications d'acquisition de données en temps réel.
DoEvents a tendance à ralentir le système, car il augmente la quantité de traitement que le système doit effectuer pour chaque message. Vous devez n'utiliser DoEvents que lorsque cela est nécessaire, et « l'arrêter » dès que possible.
L'exemple du clic sur un bouton de commande est le plus fréquent. Que se passe t'il lorsque l'utilisateur clic deux fois (par impatience) sur un bouton dont le code contient un DoEvents ?
Dan Tohatan :
Imaginez ceci : Vous avez un bouton sur votre formulaire qui, une fois cliqué, fait un traitement complexe. Pendant ce traitement complexe, il appelle également de façon intermittente DoEvents afin de garder l'interface utilisateur de l'application "réactive" - pas la meilleure méthode, mais nous parlons d'un programmeur médiocre ici. Quoi qu'il en soit, l'utilisateur voit l'application toujours en action mais sans aucune indication qu'il y a un processus en cours. Ainsi, l'utilisateur clique à nouveau sur ce bouton pendant que le traitement se passe! A cause du DoEvents, La touche répond à l'événement et commence un autre thread de traitement. Donc, comme je l'ai dit plus tôt, DoEvents « fout en l'air » le flux de l'application trop facilement.
Private Sub CommandButton1_Click() If TextBox1 = "" Then TextBox1 = "Texte 1" Else TextBox1 = "Second lancement" End If For i = 1 To 500000000 DoEvents Next End Sub
Constatations : Une fois que vous aurez cliqué deux fois sur votre bouton, vous pourrez fermer la Form grâce à la petite croix de fermeture. Cependant, votre code sera toujours en train de tourner. Vous ne pouvez pas repasser en mode création, à moins de ne « breaker » votre macro en frappant sur les touches Ctrl+Pause.
Comme nous l'avons dit précédemment : utiliser DoEvents en boucle ne peut que solliciter abusivement le processeur et en provoquer la surchauffe. Il ne s'agit d'augmentation de mémoire utilisée, mais de sollicitation abusive.
ucfoutu :
Insérez une TextBox (TextBox1) et un bouton de commande (Commandbutton1) et ce code :
Private Sub CommandButton1_Click() Chrono 300, Now End Sub Sub Chrono(duree As Double, depart As Double) Dim p As Double Do While depart - duree > CDbl(TimeValue(Now)) If ArretJeu = True Then Exit Sub TextBox1.Value = Right(Format(duree - (CDbl(TimeValue(Now)) - depart), "hh:mm:ss"), 5) DoEvents Loop End Sub
Mettez en route le gestionnaire de tâches, onglet performances et, parallèlement lancez en cliquant sur le bouton de commande. Observez ce qui se passe alors (dans la petite "fenêtre" la plus à droite relative à l'utilisation de l'UC). L'outil constate à ce moment-là une augmentation (croissante ensuite) de la température. Sur ma vieille machine de test (utilisée exprès car moins bien réfrigérée) on entend même le ventilateur de refroidissement du processeur commencer à s'affoler.
Faites un break (CTRL + PAUSE) de la macro, tout se "calme".
Il ne faut donc pas utiliser DoEvents en boucle sur une si longue durée. Va bene sur des petites "attentes", mais pas sur de longues périodes.
En effet, il augmente la quantité de traitement que le système doit effectuer pour chaque message.
ucfoutu :
Sur un userform :
- deux boutons (un pour lancer et l'autre pour arrêter)
- un label
Private Declare Function GetInputState Lib "user32" () As Long Private toto As Boolean Private Sub Commandbutton1_Click() Dim nb_iterations As Long nb_iterations = 3000000 'le Label1 n'est là que pour choisir le moment de l'interruption souhaitée ' par click sur le bouton command2 ' tester d'abord à fond, sans interrompre ' Vérifier ensuite que vous pouvez interrompre, dans les deux cas. ' Le Me.Caption vous permettra de savoir quelle boucle (avec ou sans) vous interrompez Label1.Move 0, 0, Me.Width, 20 Label1.FontSize = 14 Label1.Caption = "traitement avec Doevents seul en cours" Me.Caption = "tout" temps_sans = test(nb_iterations, False) ' false pour ne pas subordonner à la seule appli en cours Label1.Caption = "traitement avec Doevents limités aux événements de l'appli en cours" DoEvents 'ce doevents n'est là que pour permettre la mise à jour du Label Me.Caption = "avec contrôle" temps_avec = test(nb_iterations, True) ' true pour subordonner à la seule appli en cours If Not toto Then MsgBox "il a fallu :" & vbCrLf & _ temps_sans & " secondes en utilisant uniquement Doevents" & vbCrLf & "et seulement " & _ temps_avec & " secondes en utilisant un Doevents ne concernant que l'appli en cours" & vbCrLf & _ "pour faire les mêmes " & nb_iterations & " itérations." End If End Sub Private Sub Commandbutton2_Click() toto = True ' pour interrompre la boucle End Sub Private Function test(nb_iterations As Long, comment As Boolean) As Long depart = Timer toto = False For i = 1 To nb_iterations If comment = True Then If GetInputState <> 0 Then DoEvents ' un doevents "subordonné" Else DoEvents ' un Doevents non "subordonné" End If If toto Then Exit For Next test = Timer - depart End Function
Constatation : Il faut être patient pour 300 000 itérations. Vous pouvez réduire ce nombre si la patience n'est pas votre qualité principale. Le temps d'exécution entre les deux méthodes utilisés est saisissant.
Extrait d'une entrevue disponible ici : http://blog.codinghorror.com/is-doevents-evil/
Question: Pour qu'elle raison avoir maintenu DoEvents dans le .NET framework? Pourquoi ne pas l'avoir limité à une association dans l'espace de noms Microsoft.VisualBasic? Qu'elle est l'utilité de DoEvents lorsqu'il existe un support approprié pour les applications Multi-Thread?
Réponse: Jason (Microsoft): DoEvents est un vestige de VB 5.x, mais il est toujours utile dans le monde de .NET Framework. Si vous avez une boucle qui fonctionne pendant une longue période, il est souvent plus facile d'appeler DoEvents que de remanier votre boucle pour utiliser un véritable multitâche (threading) .NET.
Q: Pourquoi DoEvents est-il dans l'espace de noms BCL?
R: Glenn (Microsoft): Le multitâche est difficile, et devrait être évité s'il y a un moyen plus simple. Si tout ce que vous avez besoin dans votre thread d'interface utilisateur c'est du rendement, DoEvents est parfait.
Q: DoEvents est-il le diable?
A: Glenn (Microsoft) : La souplesse dans votre thread d'interface utilisateur est une pratique légitime dans la programmation Windows. Cela a toujours été. DoEvents rend les choses faciles, parce que les situations dans lesquelles vous avez besoin de l'utiliser sont simples.
Que substituer à ce "mécanisme" alors ?
Plusieurs pistes s'offrent à nous.
Vous pouvez avoir besoin d'exécuter du code périodiquement et automatiquement. Pour cela, vous pouvez faire appel à la méthode OnTime de l'application afin d'exécuter automatiquement une procédure. Cette méthode permet de planifier l'exécution d'une action spécifique lorsqu'un délai donné s'est écoulé ou de planifier l'exécution d'une action à une heure définie. Elle met donc en attente l'exécution d'une action. Contrairement à d'autres méthodes (Application.Wait...), avec Application.Ontime, l'utilisateur ne perd jamais la main.
L'idée ici est de créer un appel, à intervalle régulier, de notre fonction, à partir d'elle-même. En s'auto-appelant toutes les x secondes, elle laisse le temps, à l'utilisateur, de réaliser des actions.
Chip Pearson :
http://www.cpearson.com/excel/ontime.aspx
Comme paramètres, la méthode OnTime prend l'heure précise à laquelle elle doit exécuter la procédure et le nom de la procédure à exécuter. Si vous devez annuler un OnTime, vous devez fournir l'heure exacte à laquelle l'événement a été calendrier avoir lieu. Il n'y a pas moyen de dire à Excel d'annuler le prochain événement OnTime ou d'annuler tous les événements OnTime attente. Par conséquent, vous avez besoin de stocker l'heure à laquelle la procédure est à exécuter dans une variable publique et utiliser la valeur de cette variable dans les appels à OnTime.
Illustrations :
Exemple 1
Regardez ce que fait cet exemple, sans aucun DoEvents :
Dans un module :
Public laps As Integer Public Sub mon_timer() If laps <= 0 Then msgbox "c'est fini": Exit Sub MsgBox "coucou" Application.OnTime Now + TimeValue("00:00:02"), "mon_timer" laps = laps - 2 End Sub
Ajoutez deux boutons de commandes : CommandButton1 et CommandButton2.
Leurs codes respectifs seront :
Private Sub CommandButton1_Click() 'code d'appel de la fonction montimer avec OnTime laps = 300 Application.OnTime Now + TimeValue("00:00:01"), "mon_timer" End Sub Private Sub CommandButton2_Click() 'Arret Ontime laps = 0 End Sub
Toutes les deux secondes, la procédure mon_timer est appelée par OnTime. L'utilisateur peut continuer de travailler entre ces appels.
Exemple 2
Dans cet exemple, il s'agit d'alimenter une boucle (pour une fausse progress_barre par exemple) tout en maintenant le bouton appuyé. La boucle stoppe lorsque l'utilisateur « relâche » le bouton.
Code du bouton de commande :
Private Sub CommandButton1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) actif = True Application.OnTime Now, "BarreDeProgression" ' on "lance" sans attendre End Sub Private Sub CommandButton1_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) actif = False End Sub
Dans un Module :
Private der As Double, cpt As Long Public actif As Boolean Public Sub BarreDeProgression() If Timer > der + 0.05 Or der = 0 Then cpt = cpt + 1 ' | ou toutes autres UserForm3.Label1.Width = cpt ' | actions de ton choix DoEvents ' un doevents qui n'interviendra que si condition remplie ' (si + de 0,05 sec écoulées) der = Timer End If If actif Then Application.OnTime Now, "BarreDeProgression" ' on "relance" aussitôt" Else cpt = 0 der = 0 End If End Sub
Cette fonction ne se substitue pas à DoEvents, mais va nous permettre d'en contrôler et donc de maitriser son utilisation.
Cette API Windows, fonction de type Boolean, intercepte le clic de la souris ou la frappe des touches de votre clavier. Tant qu'aucune de ces actions n'est faite par l'utilisateur, elle retourne 0. Si un clic ou une touche a été pressée, elle retourne une valeur non-nulle.
L'intérêt ici est de n'utiliser DoEvents qu'à bon escient, uniquement lorsque l'utilisateur réalise une action dans le cadre de votre application. En utilisant GetInputState, vous limitez le nombre de DoEvents aux seuls événements de l'application en cours.
En guise d'exemple, vous pouvez vous reportez au chapitre Illustrations / Exemples, paragraphe DoEvents a tendance à ralentir le système
Cette méthode de substitution utilise les API SetTimer et KillTimer pour créer une procédure événementielle. Celle-ci va donc "lancer" à intervalles réguliers une fonction, que vous désignerez dans le code.
Cet exemple est basé sur celui disponible sur le site de microsoft à cette adresse : http://support.microsoft.com/kb/180736/fr
Il va s'agir d'alimenter progressivement une progressBar (ici un label dont la propriété Width sera incrémentée de 1 en 1) pendant que l'utilisateur saisit des données dans un TextBox.
Dans cet exemple, l'intervalle d'exécution est de 200 millisecondes (0,2 sec) et la fonction a pour nom : TimerProc
Dans un module :
Option Explicit 'Les API Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, _ ByVal lpTimerFunc As Long) As Long Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long 'variables globales Public iCounter As Integer Public lngTimerID As Long Public BlnTimer As Boolean 'Votre procédure à déclencher à intervalles réguliers Sub TimerProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long) iCounter = iCounter + 1 'Pour stopper la relance de cette procédure depuis elle-même : 'If iCounter = 50 Then Call Arret_Timer: Exit Sub UserForm1.Label1.Width = UserForm1.Label1.Width + 1 UserForm1.Label1.Caption = CStr(iCounter) End Sub 'La procédure de commencement du Timer Public Sub Lance_Timer() '200 = intervalle en millisecondes 'TimerProc = nom de la procédure à relancer toutes les 200ms lngTimerID = SetTimer(0, 0, 200, AddressOf TimerProc) If lngTimerID = 0 Then MsgBox "Timer non créé. Fin du programme." Exit Sub End If BlnTimer = True End Sub 'La procédure d'arrêt du Timer Public Sub Arret_Timer() lngTimerID = KillTimer(0, lngTimerID) If lngTimerID = 0 Then MsgBox "Ne peut pas arrêter le Timer." Else UserForm1.CommandButton1.Caption = "En cliquant vous lancerez" & Chr(13) & _ "la ""progress-barre"" et " & Chr(13) & _ "pourrez toujours saisir dans le textbox" UserForm1.Label1.Width = 0 End If BlnTimer = False End Sub
Dans un UserForm (UserForm1), placez : un CommandButton (CommandButton1), un Label (Label1), un TextBox (TextBox1) et un second bouton (CommandButton2).
Le code :
Option Explicit Private Sub CommandButton1_Click() If BlnTimer = False Then Call Lance_Timer Label1.Width = 0 CommandButton1.Caption = "Stop" Else Call Arret_Timer End If End Sub Private Sub CommandButton2_Click() [G33] = TextBox1 TextBox1 = "" End Sub Private Sub UserForm_Initialize() BlnTimer = False CommandButton1.Caption = "En cliquant vous lancerez" & Chr(13) & _ "la ""progress-barre"" et " & Chr(13) & _ "pourrez toujours saisir dans le textbox" CommandButton2.Caption = "Insérer TextBox1.Text en cellule G33" End Sub
Nota : Si le code exécuté par le Timer change une valeur de cellule, et que vous êtes en mode d'édition dans Excel, pour, par exemple, la saisie des données dans une cellule, Excel va probablement planter complètement et vous perdrez tout travail non enregistré. Utilisez Windows Timer avec prudence.
Conseils :
DoEvents devrait n'être utilisé que pour des choses simples comme permettre à l'utilisateur d'annuler un processus après qu'il ait commencé ou le rafraichissement d'un UserForm. Pour les opérations nécessitant une exécution plus longue, l'opérateur pourra plus aisément rendre la main au processeur s'il a recours à un contrôle Timer ou à un composant EXE ActiveX. Dans ce cas, la tâche s'effectue de manière complètement indépendante, hors de votre application, le système d'exploitation gérant à la fois le multitâche et le partage du temps.
Un très grand merci à ucfoutu. Rendons à César...
http://www.excel-downloads.com/forum/33720-doevents-explication.html
http://blog.codinghorror.com/is-doevents-evil/
http://support.microsoft.com/kb/118468/fr
http://support.microsoft.com/kb/180736/fr
http://www.cpearson.com/excel/ontime.aspx
Ce tutoriel est un complément de : celui-ci. Vous pourrez y trouver un exemple d'utilisation.
15 oct. 2020 à 09:22