par , 23/08/2019 à 23h14 (4046 Affichages)
Salut.
On voit des horreurs sur le forum. Dans cette discussion, "on" propose même un unload sauvage lorsqu'une donnée n'est pas bien remplie.
Voici comment il me semble professionnel d'architecturer son code lorsque l'on utilise un userform qui nécessite une validation de données.
Validation "technique"
La validation technique consiste à vérifier que le userform a reçu des infos qui correspondent aux types de données attendues. Si un textbox doit contenir une date, c'est le rôle du userform de vérifier que c'est bien du texte qui pourra être converti en date qui a été saisi (éventuellement selon le bon format de saisie). Si le textbox doit contenir une valeur numérique, c'est également le rôle du userform de vérifier cela.
Par contre, ce n'est pas le rôle du userform de vérifier que la date est valide selon une règle de gestion (pas de date ultérieure à la date du jour, date devant être comprise dans une plage de date précise, ...), car cette vérification dépend du métier géré par l'application.
Validation "Métier"
La validation "métier" n'a rien à faire dans le userform. Sans vouloir être un puriste de l'architecture "trois tiers" (quoique, lorsqu'on y a goûté, on ne sait plus s'en passer, même en VBA pour Excel), le userform dépend de la couche de présentation (Presentation Layer ou PL), les règles "métier" dépendent de la couche métier (Business Layer ou BL). il conviendra donc de coder de façon à respecter cette séparation des responsabilités. Nous allons voir comment.
Je précise toutefois que l'architecture proposée ici n'est pas une architecture "trois tiers pur".
Et les messages d'avertissement pour l'utilisateur, on les met où?
Toujours dans l'esprit de tendre vers le "trois tiers", voire même de faire du "trois tiers" pur, les messages à destination de l'utilisateur ne peuvent se trouver que dans la PL (Presentation Layer) et jamais ailleurs (ni dans la BL, ni dans la DAL -> Data Access Layer). Idéalement, c'est la procédure déclenchée par l'utilisateur qui doit afficher les msgbox éventuels, en fonction des réponses reçues par la couche métier.
Ce qui est dit ci-dessous est valable pour un userform modal (qui bloque l'accès à l'appli tant qu'il est visible), mais peut également être appliqué à un userform non modal.
Temps de vie d'un userform
- Chargement (Load userform);
- Préparation du userform;
- Affichage du userform (Userform.Show);
- Masquage du userform, ce qui rend la main au code appelant (Me.Hide);
- Utilisation des données saisies dans le userform;
- Déchargement du userform (Unload Userform).
Le point 1 est facultatif car dès que l'on utilise le userform, même pour le décharger, on le charge d'abord de façon implicite s'il n'est pas déjà en mémoire. Notez au passage qu'il est de ce fait impossible de tester useform Is Nothing qui renverra toujours False. (Attention, je parle bien du userform et non d'un objet pointant vers le userform...)
Architecture du code
- Une procédure charge le userform, le prépare, l'affiche, puis, après masquage, gère les infos y saisies et le décharge.
- Si besoin d'une vérification "métier", elle est extérieure au userform et prendra souvent la forme d'une fonction booléenne(*) recevant en arguments les données nécessaires à la validation. Cette fonction sera appelée par le userform de façon indirecte, pour permettre d'utiliser le même userform avec des fonctions de vérification métier différentes. Nous allons voir comment plus loin.
- Au sein du userform, une fonction a priori booléenne(*) permettra de vérifier la validité technique des données saisies. Cette fonction sera appelée au moment du click sur le bouton de validation. A priori, la validation technique aura lieu avant la validation métier, car il ne sert à rien de vouloir valider des données qui n'ont pas le bon type, par exemple.
- Un clic sur le bouton de validation devra donc simplement appeler les deux validations, masquer le userform si tout est ok et rendre la main au code appelant en lui permettant de connaître le bouton cliqué, et afficher les messages d'erreur en cas d'échec d'une des validations, sans masquer alors le userform, ou à tout le moins en laissant l'utilisateur choisir de fermer ou pas le userform.
- Après masquage du userform par lui-même, le code appelant reprend la main pour traiter les infos saisies puis décharger le userform.
Bien entendu, l'architecture proposée ici est minimaliste, mais tout ajout de code doit respecter cette architecture: les interactions avec l'utilisateur dans le userform, en ce compris le contrôle technique, et tout le reste en dehors du userform. Les couches BL et DAL ne doivent jamais interagir directement avec l'utilisateur.
Procédure d'appel et de gestion du userform
Dans le respect de ce qui précède, voici la procédure externe au userform qui le gère de A à Z.
1 2 3 4 5 6 7 8 9 10 11 12 13
| Sub test()
With UserForm1
.tboData.Value = "Bonjour"
.BusinessCheckFunction = "ValueIsOk"
.Show
If .Result = "Validate" Then
Range("a1").Value = .tboData.Value
Else
MsgBox "Vous avez abandonné la saisie"
End If
End With
Unload UserForm1
End Sub |
Cette procédure charge implicitement le userform puis le prépare avec les lignes suivantes, qui charge une valeur par défaut dans un textbox et qui affecte à une propriété publique du userform le nom de la fonction de validation "métier" qui doit se trouver dans un module standard et dont le nom doit être unique dans tout le code de l'application (il n'est donc pas question que cette fonction ait le même nom qu'une autre d'un autre module standard, elle doit être unique dans les modules standards).
1 2
| .tboData.Value = "Bonjour"
.BusinessCheckFunction = "ValueIsOk" |
Après, le userform est affiché. La suite du code testant le bouton cliqué et décidant de la suite se passe de commentaires. Ici, on affecte directement une cellule, mais bien entendu, dans les faits, cette affectation passera probablement par une procédure gérant éventuellement une structure (Type... End Type), un objet personnalisé, la gestion d'une table de données (tableau structuré).
Fonction de vérification "Métier"
Cette fonction, ici booléenne, reçoit en arguments les données à valider. L'intérêt de la sortir du userform, outre le respect du "trois-tiers", permet de l'utiliser dans un autre processus. Le fait que cette fonction n'interagisse pas avec l'utilisateur (msgbox par exemple), outre ici aussi le respect du trois-tiers, permet de l'utiliser pour vérifier des données en provenance d'une autre source que le userform (par exemple, une boucle qui lit un xml, un txt ou un recordet, où l'on imagine mal un msgbox intempestif bloquant le déroulement de la boucle).
Ici, la fonction est volontairement courte et sert uniquement à illustrer l'architecture mise en place.
1 2 3
| Function ValueIsOk(Value As String) As Boolean
ValueIsOk = (UCase(Value) <> "BONJOUR")
End Function |
Code du userform
Voici le code du userform. On remarque au début du module la déclaration de la propriété publique qui signalera le bouton cliqué et celle qui contiendra le nom de la fonction de validation "métier".
On remarque que le bouton Validate appelle les fonctions de vérification en commençant par la technique (présente dans le userform), puis celle de la validation "métier" appelée par la fonction Run, qui reçoit en premier argument le nom de la fonction à utiliser et qui doit recevoir à la suite les arguments nécessaires à la fonction. C'est l'utilisation de Run en tant que fonction qui permet d'externaliser la fonction métier qui, je le rappelle, n'a rien à faire dans le userform. C'est Run qui permet également d'utiliser le userform à différents endroits en faisant varier la fonction "métier" de vérification. Utilisé comme une fonction, Run renvoie "par ricochet" la valeur de la fonction passée en premier argument. En mettant un point d'arrêt sur le click du bouton du formulaire et en avançant pas à pas (F8), on voit bien comment les choses se passent.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| Option Explicit
Public Result As String
Public BusinessCheckFunction As String
Private Sub btnCancel_Click()
Me.Hide
End Sub
Private Sub btnValidate_Click()
If DatasAreOk() Then
If Run(BusinessCheckFunction, tboData.Value) Then
Result = "Validate"
Me.Hide
Else
MsgBox "Les données ne respectent pas les règles de gestion"
End If
Else
MsgBox "Vous devez saisir une valeur"
End If
End Sub
Function DatasAreOk() As Boolean
DatasAreOk = (tboData.Value <> "")
End Function |
Conclusions
- Avec VBA comme avec n'importe quel langage, on peut faire de la belle ouvrage ou de la mer***... A chacun ses choix;
- Systématiser votre approche du code en vous inspirant de ce billet va rendre votre code plus stable, plus lisible, plus maintenable;
- Il existe des "design pattern", validés par la communauté des programmeurs, et il ne sert à rien de réinventer la roue, juste de connaître, comprendre et appliquer les bonnes pratiques;
- L'architecture "Trois Tiers" fait partie des bonnes pratiques de programmation en VBA/EXCEL et
peut doit être respectée même pour des petits projets. Elle permet une approche professionnelle du code et limite les risques d'erreurs et de plantage; - e lis souvent sur les forums que les pratiques se valent, mais je suis clairement d'un autre avis. Il y a beaucoup de pratiques pour arriver à un même résultat, mais beaucoup sont brouillonnes, peu sont bonnes et une, voire deux, sont vraiment à ranger dans les bonnes pratiques. Pour arriver au même résultat lors de la manipulation d'un userform, on peut coder de façon impropre ou en respectant les règles de bonnes pratiques. A chacun de choisir son camp... Vous avez choisi le vôtre?
Bon travail dans le développement de vos applications VBA (ou autres...)