[VBA] Créer un Userform solide en utilisant les classes - architecture MVC
Bonjour à tous,
On peut souvent trouver sur le forum des projets d'UserForm assez lourds, et extrêmement difficiles à déboguer car l'ensemble (ou quasiment) du code se trouve dans le module de l'UserForm. C'est une approche certes pratique, surtout en prototypage, mais c'est difficile à maintenir quand le projet devient plus conséquent.
Une première solution consiste à déplacer la partie "logique" du code dans des macros/fonctions de module standard, et d'appeler ces macros depuis l'UserForm. On peut parfois aussi créer des petites classes pour gérer les interactions sur un type de contrôle spécifique. Mais ce sont des solutions intermédiaires, qui ont aussi leurs limites.
Une approche plus courante en programmation est de suivre un "modèle" (qu'on appellera aussi architecture) standard clair, qui par nature évite un certain nombre de problèmes. Il en existe plusieurs, aucun n'est parfait, mais je vais vous présenter le plus courant : le MVC "Modèle-Vue-Contrôleur".
Nous allons dans ce fil créer une mini calculatrice (très simple) afin d'illustrer le modèle MVC en VBA.
Qu’est-ce que le MVC ?
Le MVC (Modèle – Vue – Contrôleur) est une architecture logicielle qui vise à séparer les responsabilités dans une application.
L’idée est simple : au lieu de tout mettre dans un seul bloc de code (comme souvent dans un UserForm), on découpe en trois parties bien distinctes :
- Modèle (Model)
C’est la partie qui gère les données et la logique métier (on dit métier car c'est souvent là qu'on effectuera les calculs spécifiques à un métier : comptabilité/ingénierie/etc. c'est la partie "technique"). Elle ne connaît ni l’interface utilisateur ni Excel directement (dans notre exemple, elle sait calculer une somme, une moyenne, une médiane, mais elle ignore tout du UserForm). - Vue (View)
C’est l’interface utilisateur : ici, notre UserForm. Elle se contente d’afficher des informations et de remonter les actions de l’utilisateur (clics, saisies). Elle ne fait aucun calcul et ne parle pas directement au modèle. - Contrôleur (Controller)
C’est le chef d'orchestre. Il écoute les événements de la Vue, récupère les données saisies, les valide, appelle le Modèle pour faire les calculs, puis renvoie les résultats à la Vue. C’est aussi lui qui gère les interactions avec Excel (lecture/écriture dans la feuille).
En résumé :
- Modèle = "Quoi faire" (logique métier)
- Vue = "Comment on le montre" (interface utilisateur "UI")
- Contrôleur = "Quand et dans quel ordre" (orchestration/gestion)
Cette séparation rend le code plus clair, plus testable et plus maintenable. Si demain on veut changer la logique métier (dans notre exemple, le calcul d'une somme), il suffit de modifier le Modèle sans toucher à la Vue ni au Contrôleur. De même, si l'on souhaite refaire le design de l'interface (UserForm), on changera simplement la Vue sans casser le reste.
Maintenant que nous avons compris pourquoi et comment séparer les responsabilités dans notre application, voyons comment cela se traduit dans l'exemple de la calculatrice.
La classe Modèle Model
On va créer une classe afin de regrouper toutes les fonctionnalités (internes) de notre calculatrice. On utilise une classe plutôt qu'un simple module car cela a plusieurs avantages :
- D'une part, on regroupe notre code dans des blocs avec chacun une fonctionnalité claire et définie, dans un module on peut mettre un peu tout et n'importe quoi.
- On peut sauvegarder dedans des valeurs (des attributs) et le modèle pourra les utiliser en interne pour les calculs. Tout cela en privé sans "polluer" l'espace global accessible partout dans le code. Ça limite les erreurs.
- Enfin, si l'on décide de faire une application vraiment complexe, on pourra lancer plusieurs calculatrices en parallèle qui vont chacune utiliser leur modèle SANS AUCUN SOUCIS de données partagées : chacune travaille dans son petit monde isolé.
- Bref, c'est de l'organisation, et des bonnes pratiques qui permettent d'éviter les problèmes avant même qu'ils n'apparaissent.
Bon maintenant, que doit contenir notre modèle ? Et bien c'est très simple : "Que doit savoir faire notre application ?"
Dans le projet calculatrice, le modèle doit :
- prendre 3 nombres et calculer leur SOMME, leur MOYENNE, et leur MEDIANE.
Donc je vais mettre dans mon modèle une fonction pour chacun de ces calculs, et 3 attributs pour stocker les 3 nombres (j'ai fait une liste, donc en pratique 1 attribut, mais l'idée c'est de stocker les valeurs utiles).
Rien d'autre !
La classe Vue (l'UserForm)
La Vue, c’est notre interface utilisateur, autrement dit notre UserForm. Son rôle est très simple : afficher des informations et remonter les actions de l’utilisateur. Rien d’autre !
Concrètement, la Vue doit :
- Afficher les champs de saisie (dans notre exemple, les trois TextBox pour les valeurs).
- Afficher les résultats (les trois TextBox pour la somme, la moyenne et la médiane).
- Proposer les actions (boutons pour lire/écrire dans Excel, fermer la fenêtre).
- Informer l’utilisateur (par exemple, un label pour afficher un message d’erreur ou d’information).
Et c’est tout. Elle ne doit pas :
- Faire des calculs (même pas une addition !).
- Accéder directement à Excel.
- Connaître le modèle ou la logique métier.
En pratique, la Vue se contente d’émettre des événements (par exemple : "l’utilisateur a changé une valeur", "il a cliqué sur Exporter") et d’exposer des méthodes simples pour que le contrôleur puisse mettre à jour l’affichage (par exemple : "affiche ces résultats", "montre ce message").
Pour cela on va utiliser les Events VBA. Ça peut faire peur mais en réalité ce n'est pas très compliqué à comprendre : dans le code UserForm en haut du module on va définir une liste des évènements qui peuvent arriver ("Entrée modifiée", "Demande de fermeture"…). Puis, on va utiliser Instruction RaiseEvent pour "lever" ou "créer" un évènement correspondant, qui sera attrapé par le contrôleur. Pour attraper les évènements, le contrôleur aura besoin d'utiliser WithEvents Private WithEvents maVue As UserForm.
Dans le projet calculatrice, la vue doit pouvoir :
- Afficher les 3 textbox d'entrées des 3 nombres
- Afficher les 3 textbox de résultats
- Proposer un import et export dans Excel avec 2 boutons
- Un bouton quitter
Bref, la Vue répond à la question : "Comment on le montre à l’utilisateur ?" et rien d’autre.
La classe Contrôleur
Le Contrôleur est le gestionnaire. Il ne fait pas de calculs, il ne dessine pas l’interface : il coordonne.
Son rôle est de :
- Écouter les événements de la Vue (par exemple : "l’utilisateur a changé une valeur", "il a cliqué sur Exporter").
- Valider les données (sont-elles correctes ?).
- Appeler le Modèle pour effectuer les calculs.
- Mettre à jour la Vue avec les résultats ou les messages d’erreur.
- (Bonus) Gérer les interactions avec Excel (lecture/écriture dans la feuille).
En résumé, le contrôleur répond à la question : "Quand et dans quel ordre les choses doivent se faire ?"
Pourquoi une classe dédiée ?
- Pour éviter que la Vue prenne le contrôle (ce qui arrive souvent dans les UserForms monolithiques) et devienne gigantesque.
- Pour centraliser la logique de coordination et la rendre facile à maintenir.
- Pour respecter le découplage : la Vue ne connaît pas le Modèle, et inversement. Cela permet des modifications dans chaque partie indépendamment sans planter tout le projet.
Dans le projet calculatrice, le contrôleur doit :
- Vérifier que les valeurs des textboxes sont numériques
- Afficher des messages d'alerte le cas échéant
- Si tout est ok, mettre à jour le calcul des résultats (en les demandant au modèle)
- Petit plus : interagir avec Excel : exporter et importer les valeurs des textboxes depuis une plage.
Voilà, ci-joint vous trouverez le classeur correspondant avec le code commenté. À décortiquer avec cet article en parallèle. Si vous avez des questions ou des points à clarifier (je pense aux Events notamment) n'hésitez pas à demander et je pourrais vous proposer un autre tutoriel.
Bonne journée !
Rappels/conseils :
À ne pas faire (erreurs classiques)
Dans le Modèle
- Manipuler des objets Excel (Range, Worksheet).
- Gérer des événements ou des interactions UI.
- Dépendre du contrôleur ou de la Vue.
Dans la Vue (UserForm)
- Mettre des calculs dans le code du UserForm.
- Accéder directement à Excel (ex. Range("A1").Value = ...).
- Appeler des méthodes du Modèle ou du Contrôleur directement (couplage fort).
Dans le Contrôleur
- Faire des calculs métier (ça appartient au Modèle).
- Dessiner ou gérer la mise en forme de l’UI (ça appartient à la Vue).
- Créer des liens directs entre Vue et Modèle (ex. mView.txtA.Text = mModel.Value).
Petite update : ci-après démo avec l'userform en vbModeless pour interactions dans Excel en direct
https://jumpshare.com/s/lI9Ro3qOXrtxVd7XxFuA
Fichier MAJ pour supporter le modeless
Bonjour saboh,
Merci c'est super intéressant ! Je vois que tu as un certain niveau de programmation et de bonnes connaissances dans le milieu ! J'avais commencé à créer un logiciel de GMAO dans Excel mais j'avais tout mis dans l'Userform gloups...
Tu as précisé au début, qu'aucune architecture n'est parfaite. Quelles sont les limites de la MVC ?
Bonjour Baboutz, et merci pour ton retour.
Sur le papier l'architecture MFC est intéressante, mais dans la pratique elle a des imperfections :
Principalement, c'est lourd. C’est-à-dire que :
- Il y a plus de code à produire, et cela est encore plus visible pour de petits projets où ce code n'est pas "réutilisé"/partagé (par exemple la gestion sur le clic d'un bouton "Annuler" qui pourrait être utilisé plusieurs fois pour un gros UF, mais seulement 1x pour un petit).
- C'est très rigide comme organisation, et cela demande un effort de réflexion important pour respecter la séparation des responsabilités.
- Ce n'est pas le plus adapté pour les interfaces très dynamiques (car beaucoup d'appels en série => ralentit).
Ensuite concernant le programmeur :
- cela demande des connaissances de programmation plus approfondies
- et un effort de réflexion pas toujours rentable, surtout pour les débutants
Et enfin, le VBA dans Excel n'est vraiment pas le langage le plus adapté à ce type de programmation qui brille dans les langages de POO comme le Java/C/… En effet sans héritage, une gestion d'évènements assez limitée et surtout un lien très fort avec Excel en lui-même, on ne se facilite pas du tout la vie en faisant du MVC dans Excel.
Pour résumer c'est un modèle très bien pour des programmes complexes et évolutifs (comme un logiciel de GMAO), dont on veut pouvoir tester la logique "backend" de manière automatique sans ouvrir l'interface. C'est aussi intéressant quand on travaille en collaboratif pour pouvoir réutiliser du code sans avoir à fouiner s'il existe ou non un bout de code qui fait déjà la même chose. Enfin pour un programmeur plus expérimenté, c'est plus facile à relire (tu sais quoi chercher où).
Mais pour de petits projets dans Excel, rester sur un UF qui appelle des fonctions dans des modules/classes dédiées reste l'approche la plus pragmatique.