ACCESS : Créez un formulaire d'attente pour les longs traitements

Thierry GASPERMENT

Créez un formulaire d'attente pour les longs traitements.

1 - Création d'un formulaire simple.
2 - Puis ajout d'un module de classe.
3 - Et enfin ajout d'une animation.

Commentez

- Titre : ACCESS : Créez un formulaire d'attente pour les longs traitements

- Auteur : Thierry GASPERMENT

- Parution : 8 juillet 2009

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2009 Thierry GASPERMENT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- I. Introduction

- II. Formulaire simple

- II-A. Création du formulaire

- II-B. Propriétés du formulaire

- II-C. Contrôles du formulaire

- II-D. Affichage du formulaire d'attente

- II-D-1. Création d'une boucle de traitement

- II-D-2. Affichage du formulaire le temps du traitement

- II-D-3. Mise à jour du formulaire durant le traitement

- II-D-4. Gestion des erreurs

- II-D-5. Rappel du code complet

- II-E. Conclusion

- III. Pilotage par un module de classe

- III-A. Pourquoi un module de classe?

- III-B. Création du module de classe

- III-C. Les méthodes et propriétés du module de classe clProgress

- III-D. Modification du code de traitement pour affichage du formulaire d'attente

- III-E. Conclusion

- IV. Ajout d'une animation

- IV-A. Préparation des images

- IV-B. Ajout des images au formulaire

- IV-C. Gestion de l'animation

- IV-C-1. Préparation des variables et propriétés

- IV-C-2. Utilisation de la minuterie du formulaire dans le module de classe

- IV-D. Activation de l'animation

- IV-E. Conclusion

- V. Intrusivité du formulaire d'attente

- VI. Conclusion

- VII. Téléchargements

I - Introduction

Lors de traitements assez longs, nous souhaitons afficher leur progression pour indiquer à l'utilisateur qu'il doit attendre.

Nous allons apprendre à développer un formulaire d'attente comme celui-ci :

Ce tutoriel ne s'applique que si vous avez une boucle de traitement.
Il faut en effet pouvoir mettre à jour régulièrement le formulaire.

II - Formulaire simple

Pour débuter, nous allons créer un formulaire qui sera directement piloté par le code de traitement.

Voici le rendu de ce formulaire :

II-A - Création du formulaire

Créez un nouveau formulaire et sauvegardez-le sous le nom FormAttente.

II-B - Propriétés du formulaire

Modifiez les propriétés du formulaire :

Dans l'onglet Autres :

Propriété

Valeur

Explication

Fen indépendante

Oui

La fenêtre est ainsi flottante, et se déplace et se redimensionne indépendamment des autres fenêtres.

Fen modale

Oui

Une fenêtre modale empêche l'utilisateur de cliquer sur autre fenêtre.
Ainsi, l'utilisateur ne pourra agir que sur la fenêtre d'attente durant le traitement.

Dans l'onglet Format :

Propriété

Valeur

Explication

Légende

Patientez ...

Ceci est le texte affiché dans la barre de titre de la fenêtre.

Auto centrer

Oui

Pour centrer la fenêtre...

Taille ajustée

Oui

Pour ajuster la taille de la fenêtre à son contenu.

Style bordure

Trait simple fixe

On ne laisse pas la possibilité de redimensionner la fenêtre.

Afficher sélecteur

Non

Le sélecteur d'enregistrement est inutile ici.

Boutons de déplacement

Non

Les boutons de déplacements entre les enregistrements sont inutiles ici.

Diviseurs d'enregistrements

Non

Les diviseurs d'enregistrements sont inutiles ici.

Barre de défilement

Aucune

Les barres de défilements ne sont pas utiles, puisqu'on ajuste la taille de la fenêtre.

Bouton MinMax

Aucun

On n'autorise pas l'agrandissement ou la réduction de la fenêtre.

Bouton Fermer

Non

On n'autorise pas la fermeture de la fenêtre.

II-C - Contrôles du formulaire

Créez les contrôles sur le formulaire de cette manière :

Vous aurez peut-être besoin d'écrire au minimum un caractère (espace par exemple) dans les étiquettes (propriété Légende) sinon elles seront automatiquement supprimées.

Le contrôle lblInfo est une étiquette.
Choisissez par exemple une taille de caractères de 14 et une écriture en gras.
Cette étiquette servira à afficher un texte d'information fixe.

Le contrôle lblProgress est une étiquette.
Choisissez par exemple une taille de caractères de 10.
Cette étiquette servira à afficher un texte d'information variable.

Le contrôle lblProgressBack est une étiquette.
Donnez lui un style de fond standard et une couleur de fond bleu clair par exemple.

Cette étiquette servira de fond à la barre d'avancement.

Le contrôle lblProgressBar est une étiquette.
Donnez lui un style de fond standard et une couleur de fond bleu foncé par exemple.

Cette étiquette servira de barre d'avancement.

Concernant la barre d'avancement, on utilise deux étiquettes colorées superposées.
On fera varier la taille de l'étiquette lblProgressBar pour obtenir une barre de progression.

II-D - Affichage du formulaire d'attente

II-D-1 - Création d'une boucle de traitement

Créons un formulaire de test nommé FormTest qui ne contient qu'un bouton.
On va programmer un traitement dans l'évènement Sur Click de ce bouton.

Pour que le traitement soit suffisamment long, on imbrique 2 boucles dans lesquelles on place une instruction de traitement de chaîne de caractères.

Long traitement dans le formulaire de test

Option Compare Database
Option Explicit

Private Sub Commande0_Click()
Dim lCptIteration1 As Long
Dim lCptIteration2 As Long
Dim lString As String
Const lNbIterations As Long = 10000
' Boucle de traitement
For lCptIteration1 = 1 To lNbIterations
For lCptIteration2 = 1 To 1000
lString = Chr((lCptIteration1 + lCptIteration2) Mod 256)
Next
Next
End Sub

Si on affiche le formulaire de test et qu'on clique sur le bouton, le traitement s'exécute.
Durant le traitement, Access ne répond plus.
Après un instant, le curseur sablier s'affiche et "(Ne répond pas)" s'inscrit dans la barre de titre.
L'utilisateur peut penser que l'application ne fonctionne plus, ce que nous souhaitons éviter.

II-D-2 - Affichage du formulaire le temps du traitement

Nous allons encadrer notre traitement par une ouverture et une fermeture de notre formulaire d'attente Form_Attente.

Ouverture et fermeture du formulaire d'attente

Private Sub Commande0_Click()
Dim lCptIteration1 As Long
Dim lCptIteration2 As Long
Dim lString As String
Const lNbIterations As Long = 10000
' Ouverture du formulaire d'attente
DoCmd.OpenForm "FormAttente"
Forms("FormAttente").lblInfo.Caption = "Veuillez patienter durant le traitement ... "
Forms("FormAttente").lblProgressBar.Width = 0
' Nécessaire pour redonner la main à windows le temps de traiter les messages dans la pile
DoEvents
' Boucle de traitement
For lCptIteration1 = 1 To lNbIterations
For lCptIteration2 = 1 To 1000
lString = Chr((lCptIteration1 + lCptIteration2) Mod 256)
Next
Next
' Fermeture du formulaire d'attente
DoCmd.Close acForm, "FormAttente"
End Sub

Explication du code ci-dessus :
- On commence par ouvrir le formulaire FormAttente.
- Ensuite on met à jour l'étiquette lblInfo avec un texte qui sera fixe durant le traitement.
- Puis on initialise la barre de progression en donnant une largeur nulle à la barre lblProgressBar.
- Enfin on utilise DoEvents pour redonner la main à Windows qui peut alors afficher notre fenêtre à l'écran.

- En fin de traitement, on ferme le formulaire d'attente.

=> On affiche donc bien le formulaire d'attente, par contre il est figé.

II-D-3 - Mise à jour du formulaire durant le traitement

A moins d'avoir besoin d'afficher le détail de chaque itération, il n'est pas nécessaire de mettre à jour le formulaire d'attente à chaque itération de notre traitement.
Pour le test nous allons nous fixer un intervalle de mise à jour : par exemple toutes les 500 itérations.

L'opérateur Modulo (Mod en VBA) nous sera utile : il donne le reste d'une division.
On divise le compteur par 500; si le reste est égal à zéro c'est qu'on a effectué un multiple de 500 itérations.

Pour trouver le pourcentage d'avancement on est dans un cas simple :
Il suffit de diviser le compteur d'itérations par le nombre d'itérations totales.

Une fois le pourcentage d'avancement calculé on peut alors :
- modifier le texte de l'étiquette d'avancement lblProgress.
- modifier la largeur de l'étiquette lblProgressBar pour simuler une barre de progression.

Code de mise à jour de l'avancement du formulaire d'attente

Dim lPercent As Single
[...]
' Boucle de traitement
For lCptIteration1 = 1 To lNbIterations
For lCptIteration2 = 1 To 1000
lString = Chr((lCptIteration1 + lCptIteration2) Mod 256)
Next
' Toutes les 500 itérations
If lCptIteration1 Mod 500 = 0 Then
' Calcul du pourcentage d'avancement
lPercent = lCptIteration1 / lNbIterations
' Met à jour l'étiquette d'avancement
Forms("FormAttente").lblProgress.Caption = "Traitement en cours ... " & Format(lPercent, "00%")
' Met à jour la barre de progression
Forms("FormAttente").lblProgressBar.Width = Forms("FormAttente").lblProgressBack.Width * lPercent
' Repeint le formulaire
Forms("FormAttente").Repaint
End If
Next
[...]

Exécutez le traitement.
Le formulaire est bien affiché, avec mise à jour de l'image et de la progression.
Par contre l'application ne répond toujours pas.
Par exemple :
- on ne peut pas déplacer le formulaire.
- si on bascule sur une autre fenêtre, on ne peut pas réactiver l'application Access.

Il faut ajouter un DoEvents dans la boucle.

[...]
' Evite que l'application ne soit figée
DoEvents
Next
[...]

II-D-4 - Gestion des erreurs

Dans toute fonction, il est nécessaire de gérer les erreurs :
- pour afficher un message d'erreur personnalisé
- pour éviter un plantage de l'application en exécution avec le runtime

Ajoutons une gestion des erreurs :

Initialisation de la gestion d'erreurs, en début de procédure

On Error GoTo Gestion_Erreurs

Gestion d'erreurs, en fin de procédure

[...]
Exit Sub
Gestion_Erreurs:
MsgBox "Erreur lors du traitement, n° " & Err.Number & ", " & Err.Description, vbOKOnly
DoCmd.Close acForm, "FormAttente"
End Sub

En cas d'erreur on affiche alors un message et on ferme le formulaire d'attente.

II-D-5 - Rappel du code complet

Code complet du formulaire de test du formulaire d'attente

Option Compare Database
Option Explicit

Private Sub Commande0_Click()
Dim lCptIteration1 As Long
Dim lCptIteration2 As Long
Dim lString As String
Dim lPercent As Single
Const lNbIterations As Long = 10000
On Error GoTo Gestion_Erreurs
' Ouverture du formulaire d'attente
DoCmd.OpenForm "FormAttente"
Forms("FormAttente").lblInfo.Caption = "Veuillez patienter durant le traitement ... "
Forms("FormAttente").lblProgressBar.Width = 0
' Nécessaire pour redonner la main à windows le temps de traiter les messages dans la pile
DoEvents
' Boucle de traitement
For lCptIteration1 = 1 To lNbIterations
For lCptIteration2 = 1 To 1000
lString = Chr((lCptIteration1 + lCptIteration2) Mod 256)
Next
' Toutes les 500 itérations
If lCptIteration1 Mod 500 = 0 Then
' Calcul du pourcentage d'avancement
lPercent = lCptIteration1 / lNbIterations
' Met à jour l'étiquette d'avancement
Forms("FormAttente").lblProgress.Caption = "Traitement en cours ... " & Format(lPercent, "00%")
' Met à jour la barre de progression
Forms("FormAttente").lblProgressBar.Width = Forms("FormAttente").lblProgressBack.Width * lPercent
' Repeint le formulaire
Forms("FormAttente").Repaint
End If
' Evite que l'application ne soit figée
DoEvents
Next
' Fermeture du formulaire d'attente
DoCmd.Close acForm, "FormAttente"
Exit Sub
Gestion_Erreurs:
MsgBox "Erreur lors du traitement, n° " & Err.Number & ", " & Err.Description, vbOKOnly
DoCmd.Close acForm, "FormAttente"
End Sub

II-E - Conclusion

Le formulaire ainsi créé est très simple mais néanmoins efficace.

Téléchargez la base Access de ce chapitre au format ACCESS 2000

III - Pilotage par un module de classe

Pour continuer, nous allons créer un module de classe qui pilotera le formulaire d'avancement.

Dans ce chapitre, nous ne changerons rien au rendu de ce formulaire :

III-A - Pourquoi un module de classe?

Créer un module de classe nous permet ensuite de gérer le formulaire plus simplement à l'aide de méthodes et de propriétés.
Pourquoi ne pas ajouter ces propriétés et méthodes directement dans le formulaire?
En créant un module de classe on les regroupe dans un objet à part.
Ainsi on accède plus facilement aux méthodes et propriétés nécessaires au formulaire d'attente; elles ne sont, en effet, pas "noyées" parmi les autres propriétés et méthodes du formulaire :
- Le module de classe contient les propriétés et méthodes.
- Le formulaire contient uniquement les contrôles.

On pourra également facilement remplacer le formulaire par un autre, la seule condition étant que ce formulaire contienne les contrôles requis.

III-B - Création du module de classe

Dans l'éditeur VBE (ALT + F11), insérer un nouveau module de classe (Insérer => Module de classe).
Si l'instruction n'y est pas, ajoutez Option explicit en en-tête du module.
Sauvegardez le module sous le nom clProgress.

III-C - Les méthodes et propriétés du module de classe clProgress

Le module de classe clProgress doit nous aider à piloter le formulaire.
Il doit donc exposer toutes les méthodes et propriétés utiles afin de ne plus avoir besoin d'agir directement sur le formulaire.

Il nous faut pouvoir :
- Définir les textes d'information générale et détaillée (lblInfo et lblProgress).
- Définir les valeurs de la barre de progression : valeurs mini, maxi, courante, et le pas de mise à jour
- Afficher ou masquer le formulaire.
- Rafraîchir l'affichage du formulaire.

Nom

Type

Description

Visible

Propriété en lecture/écriture

Rend visible ou invisible le formulaire d'attente

GeneralInfo

Propriété en lecture/écriture

Texte d'information général.

ProgressInfo

Propriété en lecture/écriture

Texte d'information détaillée, affiché au dessus de la barre de progression.

ProgressForm

Propriété en lecture/écriture

Formulaire utilisée pour afficher la progression du traitement.

ProgressMin

Propriété en lecture/écriture

Valeur minimum de la progression.

ProgressMax

Propriété en lecture/écriture

Valeur maximum de la progression.

ProgressValue

Propriété en lecture/écriture

Valeur courante de la progression.

ProgressPercent

Propriété en lecture/écriture

Valeur courante de la progression, en pourcent.

Repaint

Méthode

Méthode à appeler pour rafraichir l'affichage du formulaire.
On placera le DoEvents ici.

Remarque : Pour la barre de progression, on définit les propriétés en double : une pour des valeurs discrètes et une pour des valeurs en pourcentage.

Les propriétés qui ne sont pas gérées par le formulaire doivent être stockées dans ce module de classe.
Par exemple la propriété GeneralInfo est stockée dans l'étiquette lblInfo du formulaire : inutile donc de rajouter une variable dans le module de classe.
Les propriétés calculées (comme la valeur en pourcentage) ne sont pas stockées.

On va donc stocker les valeurs des propriétés suivantes :
- ProgressForm
- ProgressMin
- ProgressMax
- ProgressValue

Variables de stockage, en en-tête de module

Private oProgressForm As Access.Form
Private gProgressMin As Long
Private gProgressMax As Long
Private gProgressValue As Long

Ensuite, à l'initialisation du module de classe, nous devons créer un formulaire et initialiser les valeurs des variables.

Pour pouvoir facilement créer un objet formulaire, définissez la propriété Avec Module du formulaire à Oui (voir onglet Autres des propriétés du formulaire).

Le formulaire devient alors un objet de classe, et on peut le voir dans l'explorateur d'objets de VBA :

La création d'une instance de cet objet se fait avec le mot-clé New.
Pour en savoir plus sur l'utilisation des formulaires en tant qu'objet de classe, lisez le tutoriel La notion de la classe formulaire Access par l'exemple, de Maxence Hubiche.

Le principal avantage de cette méthode par rapport à l'utilisation de OpenForm est que la liaison avec le formulaire se fait à la compilation.
Si on appelle une propriété ou méthode inconnue, on le sait à la compilation.
De plus, on bénéficie de la complétion automatique lors de l'écriture du code.

Initialisation des variables et création de l'objet formulaire

'------------------------------------------------------------
' Initialisation du module de classe
'------------------------------------------------------------
Private Sub Class_Initialize()
' Initialisation de la barre de progression
gProgressMin = 1
gProgressMax = 100
gProgressValue = 0
' Création d'un nouvel objet formulaire
Set oProgressForm = New Form_FormAttente
End Sub

Pour la création des propriétés et méthodes, si vous n'êtes pas familier avec les modules de classe, vous pouvez lire ces articles :
- Tout comprendre à propos des modules de classes sous Access, par Michel Blavin.
- Création et utilisation de classes personnalisées en VB 6.0 et VBA, par Pierre Fauconnier.

Concernant la gestion d'erreurs, il est souvent conseillé de laisser le code appelant gérer les erreurs.
Nous allons appliquer ce conseil : si une erreur est détectée dans le module de classe, elle sera renvoyée à l'appelant (notre code de traitement dans le formulaire de tests) qui se chargera d'effectuer les actions nécessaires (écriture de l'erreur dans un fichier log, affichage d'un message, poursuite ou arrêt du traitement, ...).

Nous pourrions nous contenter de ne pas capturer les erreurs.
Mais, pour faciliter la prise de décision lors d'une erreur, nous allons renvoyer depuis le module de classe l'erreur dont nous modifions la source.
Pour renvoyer l'erreur nous utiliserons la méthode Raise de l'objet Err en modifiant le paramètre Source.
Ainsi nous pourrons déterminer en fonction de la source si l'erreur provient du formulaire d'attente ou du traitement lui-même.

Définitions des propriétés et méthodes

'------------------------------------------------------------
' Visibilité du formulaire
'------------------------------------------------------------
Public Property Get Visible() As Boolean
On Error GoTo Gestion_Erreurs
Visible = oProgressForm.Visible
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
Public Property Let Visible(pVisible As Boolean)
On Error GoTo Gestion_Erreurs
oProgressForm.Visible = pVisible
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
'------------------------------------------------------------
' Information générale
'------------------------------------------------------------
Public Property Get GeneralInfo() As String
On Error GoTo Gestion_Erreurs
GeneralInfo = oProgressForm.lblInfo.Caption
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
Public Property Let GeneralInfo(pInfo As String)
On Error GoTo Gestion_Erreurs
oProgressForm.lblInfo.Caption = pInfo
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
'-------------------------------------------------------------
' Information détaillée (au-dessus de la barre de progression)
'-------------------------------------------------------------
Public Property Get ProgressInfo() As String
On Error GoTo Gestion_Erreurs
ProgressInfo = oProgressForm.lblProgress.Caption
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
Public Property Let ProgressInfo(pInfo As String)
On Error GoTo Gestion_Erreurs
oProgressForm.lblProgress.Caption = pInfo
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
'------------------------------------------------------------
' Valeur mini de la progression
'------------------------------------------------------------
Public Property Get ProgressMin() As Long
On Error GoTo Gestion_Erreurs
ProgressMin = gProgressMin
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
Public Property Let ProgressMin(pMin As Long)
On Error GoTo Gestion_Erreurs
gProgressMin = pMin
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
'------------------------------------------------------------
' Valeur maxi de la progression
'------------------------------------------------------------
Public Property Get ProgressMax() As Long
On Error GoTo Gestion_Erreurs
ProgressMax = gProgressMax
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
Public Property Let ProgressMax(pMax As Long)
On Error GoTo Gestion_Erreurs
gProgressMax = pMax
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
'-----------------------------------------------------------
' Progression en valeur
'-----------------------------------------------------------
Public Property Get ProgressValue() As Long
On Error GoTo Gestion_Erreurs
ProgressValue = gProgressValue
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
Public Property Let ProgressValue(pValue As Long)
On Error GoTo Gestion_Erreurs
' Stocke la valeur
gProgressValue = pValue
' On met à jour la barre
oProgressForm.lblProgressBar.Width = oProgressForm.lblProgressBack.Width / _
(gProgressMax - gProgressMin + 1) * (ProgressValue - gProgressMin + 1)
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
'-----------------------------------------------------------
' Progression en pourcent
'-----------------------------------------------------------
Public Property Get ProgressPercent() As Currency
On Error GoTo Gestion_Erreurs
ProgressPercent = (ProgressValue - gProgressMin + 1) / (gProgressMax - gProgressMin + 1)
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
Public Property Let ProgressPercent(pProgress As Currency)
On Error GoTo Gestion_Erreurs
ProgressValue = gProgressMin + pProgress * (gProgressMax - gProgressMin + 1)
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property

'-----------------------------------------------------------
' Raffraîchit l'affichage du formulaire
'-----------------------------------------------------------
Public Sub Repaint()
On Error GoTo Gestion_Erreurs
' Repeint le formulaire
oProgressForm.Repaint
' Evite que l'application ne soit figée
DoEvents
Exit Sub
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Sub

Ajoutons également la gestion d'erreurs dans Class_Initialize :

Initialisation du module de classe avec gestion d'erreur

'------------------------------------------------------------
' Initialisation du module de classe
'------------------------------------------------------------
Private Sub Class_Initialize()
On Error GoTo Gestion_Erreurs
' Initialisation de la barre de progression
gProgressMin = 1
gProgressMax = 100
gProgressValue = 0
' Création d'un nouvel objet formulaire
Set oProgressForm = New Form_FormAttente
Exit Sub
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Sub

III-D - Modification du code de traitement pour affichage du formulaire d'attente

Une fois que notre module est prêt, il faut l'utiliser dans la boucle de traitement.

On va repartir de notre boucle de traitement initiale :

Long traitement dans le formulaire de test

Option Compare Database
Option Explicit

Private Sub Commande0_Click()
Dim lCptIteration1 As Long
Dim lCptIteration2 As Long
Dim lString As String
Const lNbIterations As Long = 10000
On Error GoTo Gestion_Erreurs
' Boucle de traitement
For lCptIteration1 = 1 To lNbIterations
For lCptIteration2= 1 To 1000
lString = Chr((lCptIteration1 + lCptIteration2) Mod 256)
Next
Next
Exit Sub
Gestion_Erreurs:
If Err.Source = "clProgress" Then
' Ajoutez ici un éventuel code de journalisation des erreurs (fichier log)
' XXXXXX
' Puis on continue le traitement
Resume Next
Else
MsgBox "Erreur dans le traitement n° " & Err.Number & ", " & Err.Description, vbCritical
End If
End Sub

Notez la gestion des erreurs :
- on vérifie si la source de l'erreur est le module de classe clProgress.
- si c'est le cas on peut écrire l'erreur dans un fichier de log.
- puis on continue le traitement si on décide par exemple qu'une erreur dans le formulaire d'attente ne doit pas arrêter le traitement.

Pour ouvrir un formulaire d'attente, nous allons désormais créer un objet de type clProgress.
Cette objet se chargera d'ouvrir le formulaire (dans sa procédure Class_Initialize)
Le formulaire ainsi créé ne sera visible que lorsqu'on définira la propriété Visible à True, ce qui nous permet d'initialiser l'affichage avant d'afficher le formulaire.

En début de la procédure de test (Commande0_Click), on déclare l'objet :

Déclaration de l'objet

Private Sub Commande0_Click()
Dim oProgress As clProgress
[...]

Puis on crée l'objet et on initialise l'affichage avant le début de la boucle :
- Création de l'objet avec le mot-clé New.
- Définition des bornes de la barre de progression : ProgressMin et ProgressMax.
- Initialisation de la valeur de la progression : ProgressValue.
- Initialisation du texte d'information générale : GeneralInfo.
- Affichage du formulaire avec la propriété Visible.

Initialisation de l'objet

[...]
On Error GoTo Gestion_Erreurs
' Ouverture et initialisation du formulaire d'attente
Set oProgress = New clProgress
oProgress.ProgressMin = 1
oProgress.ProgressMax = lNbIterations
oProgress.ProgressValue = 0
oProgress.GeneralInfo = "Veuillez patienter durant le traitement ... "
oProgress.Visible = True
' Boucle de traitement
[...]

Pour mettre à jour le formulaire durant le traitement nous allons :
- Définir ProgressValue et ProgressInfo en fonction de l'avancement du traitement.
- Rafraichir le formulaire à l'aide de Repaint.

Mise à jour de l'avancement

[...]
' Boucle de traitement
For lCptIteration1 = 1 To lNbIterations
For lCptIteration2 = 1 To 1000
lString = Chr((lCptIteration1 + lCptIteration2) Mod 256)
Next
' Met à jour la progression
oProgress.ProgressValue = lCptIteration1
' Met à jour l'étiquette
oProgress.ProgressInfo = "Traitement en cours ... " & Format(oProgress.ProgressPercent, "00%")
' Repeint le formulaire
oProgress.Repaint
Next
[...]

On note ici que l'utilisation du module de classe est aisée : il suffit de donner à ProgressValue la valeur du compteur.
Comme on a défini au début les valeurs mini et maxi de la progression, le calcul est automatique et la barre de progression est mise à jour.
On récupère également le pourcentage à travers la propriété ProgressPercent.

Pour terminer, en fin de procédure, on libère l'objet oProgress.
Lorsqu'on libère cet objet, l'objet formulaire qu'il contient est également libéré et le formulaire FormAttente se ferme.

Libération de l'objet

[...]
' Fermeture du formulaire d'attente
Set oProgress = Nothing
Exit Sub
Gestion_Erreurs:
If Err.Source = "clProgress" Then
' Ajoutez ici un éventuel code de journalisation des erreurs (fichier log)
' XXXXXX
' Puis on continue le traitement
Resume Next
Else
MsgBox "Erreur dans le traitement n° " & Err.Number & ", " & Err.Description, vbCritical
End If
Set oProgress = Nothing
End Sub

III-E - Conclusion

Avec l'utilisation d'un module de classe, le code est mieux structuré et y gagne en clarté.

Téléchargez la base Access de ce chapitre au format ACCESS 2000

IV - Ajout d'une animation

Dans ce chapitre nous allons ajouter une animation.

Voici le rendu du formulaire avec l'animation :

Et voici un schéma récapitulatif de ce que nous allons implémenter :

IV-A - Préparation des images

Recherchez d'abord un gif animé.
Par exemple dans les Cliparts de Microsoft.
Voici le gif animé que nous allons utiliser :

Access n'étant pas capable d'afficher nativement une image animée (gif animé), nous allons la décomposer en images simples.

On peut trouver des petits utilitaires gratuits pour extraire les images d'un gif animé.

Si vous n'en trouvez pas, en voici un créé avec Access et Gdiplus :
Téléchargez l'extracteur d'images de gif animé au format ACCESS 2000
Si vous utilisez Window XP, Gdiplus est déjà intégré.
Sinon téléchargez la librairie et placez le fichier gdiplus.dll dans le même répertoire que le fichier GifExtract.mdb.

J'ai extrait pour le tutoriel les images à l'aide de GifExtract.mdb, au format PNG :

IV-B - Ajout des images au formulaire

Ajoutez des contrôles images de cette manière :

Le contrôle ImgProgress est un contrôle image standard.
C'est dans ce contrôle que s'affichera l'animation.
Choisissez un mode d'affichage égal à Zoom (onglet de propriétés Format).
Ne mettez pas d'image dans ce contrôle :
En fonction de la version d'Access utilisée, vous devrez peut-être choisir une image quelconque puis vider la propriété Image de l'onglet Format.
Les contrôles ImgFrameXX sont des contrôles image standards.
Chacun de ces contrôles contient une image de l'animation.
Insérez chacune des images (4 images dans l'exemple) dans un contrôle séparé nommé ImgFrameXX, en faisant varier XX de 01 jusqu'au nombre d'images total.
Modifiez la propriété Visible (onglet Format) de chacune de ces images, car elles ne sont pas directement affichées.

Pour visualiser l'animation dans le contrôle ImgProgress, on copiera chacune des images à tour de rôle dans ce contrôle.
Pour copier une image d'un contrôle à un autre, nous utiliserons la propriété PictureData des contrôles image.

IV-C - Gestion de l'animation

J'entrevois deux solutions pour afficher l'animation :
- la première solution serait de gérer un pas de mise à jour de l'image, de la même manière qu'on gère un pas de mise à jour de la barre de progression.
- la deuxième solution que nos allons développer est d'utiliser la minuterie du formulaire.

En utilisant la minuterie du formulaire (merci à Domi2 pour l'idée), on obtient une animation indépendante de l'avancement du traitement.
L'image sera mise à jour toutes les xxx millisecondes.

IV-C-1 - Préparation des variables et propriétés

Dans le module de classe clProgress, ajoutons les variables et propriétés nécessaires.

L'intervalle de minuterie est stocké dans le formulaire, nous n'avons donc pas besoin de variable supplémentaire pour cette valeur.
Mais il est nécessaire de créer une propriété AnimationTimer dans le module de classe pour définir cette minuterie.

Nous avons besoin de définir le préfixe des images utilisées (ImgFrame dans l'exemple).
Ce sera une propriété AnimationPrefix.
Nous stockerons non pas la valeur de ce préfixe mais la liste des noms de contrôles dans une collection gAnimationImages.

Enfin nous ajoutons une variable gAnimationValue qui contient le numéro de l'image en cours.
Cette variable sera incrémentée de 1 jusqu'au nombre d'images dans la collection.

Déclaration des variabes en en-tête du module clProgress

Private gAnimationValue As Long
Private gAnimationImages As Collection

Dans la propriété AnimationPrefix, nous allons rechercher les contrôles images dont le nom commence par ce préfixe.
Nous ajoutons ces noms de contrôle à la collection, en les rangeant par ordre alphabétique.

Propriété AnimationPrefix du module clProgress

'------------------------------------------------------------
' Prefixe des images d'animation
'------------------------------------------------------------
Public Property Let AnimationPrefix(pPrefix As String)
Dim loCtrl As Control
Dim lCpt As Long
On Error GoTo Gestion_Erreurs
' Nouvelle collection
Set gAnimationImages = New Collection
' Si le formulaire est défini
If Not oProgressForm Is Nothing Then
' Si le préfixe n'est pas vide
If pPrefix <> "" Then
' Parcourt les contrôles du formulaire
For Each loCtrl In oProgressForm.Controls
' Si c'est un control image
If TypeOf loCtrl Is Access.Image Then
' Si le nom du contrôle commence par le préfixe
If loCtrl.Name Like pPrefix & "*" Then
' Si c'est la première image on l'ajoute
If gAnimationImages.Count = 0 Then
gAnimationImages.Add loCtrl.Name, loCtrl.Name
Else
' Sinon on ajoute l'image rangée par ordre alphabétique
For lCpt = 1 To gAnimationImages.Count
If loCtrl.Name > gAnimationImages.Item(lCpt) Then
gAnimationImages.Add loCtrl.Name, loCtrl.Name, , gAnimationImages.Item(lCpt)
Exit For
End If
Next
End If
End If
End If
Next
End If
End If
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property

Dans cette propriété on crée donc une nouvelle collection.
Puis on remplit la collection avec les noms des contrôles images composant l'animation.

Notez qu'on ne stocke pas la valeur du préfixe.
On utilise la valeur donnée en paramètre pour remplir la collection.
Ensuite on n'utilise plus ce préfixe, on utilisera directement les noms des contrôles dans la collection.

Il faut enfin initialiser la collection d'images lors du chargement de formulaire.
Par défaut on recherche les images qui commencent par ImgFrame, celles de notre formulaire par défaut FormAttente.

Initialise la collection d'images dans la procédure Class_Initialize

[...]
' Création d'un nouvel objet formulaire
Set oProgressForm = New Form_FormAttente
' Images d'animation
AnimationPrefix = "ImgFrame" ' <= Ajout de l'initialisation de la collection d'images
[...]

Passons maintenant à la propriété AnimationTimer.
Cette propriété est simple, elle met à jour ou lit la valeur de la minuterie du formulaire.

Propriété AnimationTimer du module clProgress

'------------------------------------------------------------
' Minuterie de l'animation
'------------------------------------------------------------
Public Property Get AnimationTimer() As Long
On Error GoTo Gestion_Erreurs
AnimationTimer = oProgressForm.TimerInterval
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property
Public Property Let AnimationTimer(pTimer As Long)
On Error GoTo Gestion_Erreurs
oProgressForm.TimerInterval = pTimer
Exit Property
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Property

Les propriétés sont désormais définies, il va falloir s'occuper de la minuterie.

IV-C-2 - Utilisation de la minuterie du formulaire dans le module de classe

La minuterie s'exécute dans le formulaire, mais on souhaite n'écrire du code que dans le module de classe clProgress.
Il faut donc "capturer" l'évenement Sur Minuterie du formulaire dans ce module de classe.

Pour pouvoir accéder aux événements du formulaire dans le module de classe clProgress, il faut tout d'abord ajouter WithEvents à la déclaration de l'objet oProgressForm.

Modification de la déclaration du formulaire en en-tête du module

Private WithEvents oProgressForm As Access.Form

On a alors accès à l'objet oProgressForm dans la liste déroulante en haut à gauche de la page de code.

Choisissez oProgressForm dans la liste, puis ouvrez la liste en haut à droite.

Choisissez Timer dans cette liste.

La procédure de gestion de la minuterie est ajoutée :

Minuterie

Private Sub oProgressForm_Timer()

End Sub

Attention : cela ne suffit pas pour capturer le message dans le module de classe.
Il faut également préciser dans l'événement du formulaire qu'il doit exécuter une procédure événementielle sur minuterie.

Dans les propriétés du formulaire, onglet Evénement, définissez Sur Minuterie = [Procédure événementielle].

Maintenant le code défini dans la procédure oProgressForm_Timer s'exécutera sur minuterie en fonction de l'intervalle défini.

Pour animer l'image, c'est assez simple.
Nous avons défini une variable gAnimationValue que nous allons incrémenter à chaque passage dans le code de la minuterie.
Cette valeur est l'indice de la collection d'image gAnimationImages : il doit donc varier entre 1 et le nombre d'images dans la collection.
On utilise l'opérateur Mod (modulo) pour rester entre ces valeurs.

Ensuite nous affectons à l'image ImgProgress les données d'image du contrôle de la collection correspondant à cet indice.
Pour transférer une image d'un contrôle à un autre, nous utilisons la propriété PictureData.

Animation sur minuterie

'-----------------------------------------------------------
' Sur minuterie du formulaire
'-----------------------------------------------------------
Private Sub oProgressForm_Timer()
On Error GoTo Gestion_Erreurs
' Test si au moins une image dans la collection
If gAnimationImages.Count > 0 Then
' Avance d'une image
gAnimationValue = 1 + gAnimationValue Mod gAnimationImages.Count
' Affiche l'image
oProgressForm.ImgProgress.PictureData = oProgressForm.Controls(gAnimationImages.Item(gAnimationValue)).PictureData
End If
Exit Sub
Gestion_Erreurs:
Err.Raise Err.Number, "clProgress"
End Sub

Nous souhaitons ne pas attendre le premier déclenchement de la minuterie et ainsi afficher la première image dès l'activation de la minuterie.
Dans la propriété Let AnimationTimer, ajoutons un appel à la procédure de minuterie :

[...]
oProgressForm.TimerInterval = pTimer
Call oProgressForm_Timer ' <= Appel la minuterie sans attendre
[...]

IV-D - Activation de l'animation

Pour lancer l'animation, il faut définir l'intervalle de minuterie à l'aide de la propriété AnimationTimer. Dans le formulaire de test, dans la partie sur l'initialisation du formulaire d'attente et juste avant de rendre visible le formulaire :

Lance l'animation

[...]
oProgress.AnimationTimer = 500 ' <== Lance l'animation
oProgress.Visible = True
' Boucle de traitement
[...]

On définit ici un intervalle de 500 millisecondes.
On ne définit pas la propriété AnimationPrefix qui n'est pas utile si on n'utilise pas un formulaire personnalisé avec des noms de contrôles différents.

IV-E - Conclusion

Il est donc facile d'ajouter une animation au formulaire.
Il suffit de décomposer l'animation en images simples et de définir le préfixe des contrôles utilisés pour stocker ces images.
Le module de classe gère tout sans avoir besoin d'ajouter ou de modifier du code dans la boucle de traitement.

Téléchargez la base Access de ce chapitre au format ACCESS 2000

V - Intrusivité du formulaire d'attente

Vous l'avez sans doute remarqué, l'affichage du formulaire d'attente ralenti le traitement.
Pour un traitement dont les itérations sont assez rapides, ne mettez pas à jour le formulaire à chaque itération.
A moins bien sûr qu'il soit nécessaire d'afficher un texte d'avancement différent à chaque itération.
Sinon utilisez par exemple l'opérateur modulo comme exliqué dans le chapitre II.

Pour un traitement rapide, qui ne dure par exemple que 10 ou 20 secondes, on peut même envisager de n'afficher qu'un texte statique et ne pas mettre à jour le formulaire durant le traitement.

VI - Conclusion

Nous avons vu comment afficher un formulaire afin de renseigner l'utilisateur sur le traitement en cours.
Il serait à présent intéressant d'améliorer ce formulaire, par exemple en :
- gérant 2 barres de progression.
- et en encapsulant le formulaire et les modules de code dans une librairie réutilisable.

Merci à l'équipe Office pour son aide en conseils et relectures.

VII - Téléchargements

Retrouvez les bases de données de ce tutoriel à la fin de chaque chapitre.