Voir le flux RSS

Pierre Fauconnier

VBA: De la bonne programmation d'un userform

Note : 3 votes pour une moyenne de 5,00.
par , 23/08/2019 à 22h14 (990 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
  1. Chargement (Load userform);
  2. Préparation du userform;
  3. Affichage du userform (Userform.Show);
  4. Masquage du userform, ce qui rend la main au code appelant (Me.Hide);
  5. Utilisation des données saisies dans le userform;
  6. 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.
Code VBA : Sélectionner tout - Visualiser dans une fenêtre à part
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).

Code vba : Sélectionner tout - Visualiser dans une fenêtre à part
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.

Code VBA : Sélectionner tout - Visualiser dans une fenêtre à part
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.

Code vba : Sélectionner tout - Visualiser dans une fenêtre à part
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...)

Envoyer le billet « VBA: De la bonne programmation d'un userform » dans le blog Viadeo Envoyer le billet « VBA: De la bonne programmation d'un userform » dans le blog Twitter Envoyer le billet « VBA: De la bonne programmation d'un userform » dans le blog Google Envoyer le billet « VBA: De la bonne programmation d'un userform » dans le blog Facebook Envoyer le billet « VBA: De la bonne programmation d'un userform » dans le blog Digg Envoyer le billet « VBA: De la bonne programmation d'un userform » dans le blog Delicious Envoyer le billet « VBA: De la bonne programmation d'un userform » dans le blog MySpace Envoyer le billet « VBA: De la bonne programmation d'un userform » dans le blog Yahoo

Mis à jour 08/09/2019 à 17h44 par Pierre Fauconnier

Catégories
VBA , MS Office , Bonnes pratiques

Commentaires

  1. Avatar de MarcelG
    • |
    • permalink
    Salut Pierre,

    Quelle différence ferais-tu entre ce processus et celui, que tu m'as inspiré au cours de nos échanges, qui consisterait à piloter le formulaire à distance, par procédure placée dans un module standard, en l'affichant tant que l'une de ses propriétés (Property Get) ne serait pas constatée.
    Ici, la propriété serait liée à la valeur testée (booléenne ou autre). D'autre part, le bouton "OK" du formulaire n'aurait pour fonction de cacher le formulaire.
    Celui-ci serait alors déchargé depuis la procédure appelante.

    Bon après-midi et à bientôt.
  2. Avatar de Pierre Fauconnier
    • |
    • permalink
    Salut Marcel,

    je ne comprends pas ton commentaire car il me semble que c'est justement ce que j'explique ici. Une procédure appelante dans un module standard charge le formulaire, le prépare, l'affiche. Comme il est modal, le contrôle du code est passé au formulaire tant que ce dernier est visible, et une pression sur un des boutons masque le formulaire et repasse le contrôle du code à la procédure appelante, qui détermine sur quel bouton on a cliqué grâce à la propriété publique Result du formulaire et poursuit le traitement en récupérant les valeurs saisies dans les contrôles du formulaire.

    Ce n'est pas différent de ce que tu expliques, il me semble.
  3. Avatar de MarcelG
    • |
    • permalink
    Salut Pierre,

    Ce que je voulais seulement mentionner.
    En suivant un de tes post sur le forum VBA, j'utilise Property Get pour définir une propriété de formulaire.
    Tandis qu'ici, sauf erreur, la propriété Result est déclarée en entête du module affecté au formulaire.


    Pour ce qui concerne le pilotage de formulaire à distance et l'utilisation d'une variable propriété, c'est le principe que j'adopte depuis nos échanges sur le Forum.