I. Introduction▲
I-A. Pourquoi utiliser des classes personnalisées▲
On est souvent confronté, lors de l'utilisation de VB ou de VBA, au problème de « réinventer la roue » à chaque nouveau travail que l'on développe.
Si je prends l'exemple d'applications liées à la gestion d'une entreprise, nombre de nos classeurs Excel, de nos bases de données Access, voire de nos applications VB, gèrent les données de contacts.
L'utilisation de classes personnalisées va permettre un gain de temps appréciable en favorisant la réutilisation et la maintenance du code. De plus, les classes personnalisées permettent la mise à disposition d'objets complexes à des programmeurs qui pourront « se borner » à utiliser les propriétés et méthodes qu'une classe personnalisée met à leur disposition.
I-B. Objectifs▲
Ce tutoriel en trois parties a pour ambition de vous expliquer le fonctionnement des classes personnalisées en VB 6.0 ou en VBA, de vous démontrer que la mise au point de classes performantes n'est pas un travail impossible, et de vous prouver que les classes personnalisées permettent la mise à disposition d'autres programmeurs d'un code qui les soulage de la « réinvention de la roue ».
I-C. Notions abordées dans la partie 1▲
Dans cette partie, nous allons manipuler principalement les concepts liés aux propriétés d'un objet.
Pour cela, nous allons créer ce que l'on appelle une « classe métier », c'est-à-dire une classe qui reproduit en informatique un objet ou un concept que l'on voudra manipuler.
Notions abordées dans la partie 1
- Qu'est-ce qu'un objet informatique
- Qu'est-ce qu'une propriété
- Propriété en lecture seule, en écriture seule, en lecture écriture
- Encapsulation
- Génération des erreurs dans une classe et gestion par le code appelant
- Génération des événements et gestion par le code appelant.
I-D. Notions abordées dans la partie 2▲
Dans un deuxième temps, nous utiliserons notre classe « métier » vue en partie 1 en la liant à différentes sources de données au travers d'une nouvelle classe d'accès aux données.
Notions abordées dans la partie 2
- Création d'un objet personnalisé au travers d'une classe d'accès aux données
- Liaison d'un objet personnalisé à une source pour la lecture des données
- Liaison d'un objet personnalisé à une source pour la modification des données du fichier source
- Portabilité du jeu des classes
- Utilisation de données d'une base Access dans un fichier Excel grâce aux classes personnalisées
I-E. Notions abordées dans la partie 3▲
La troisième partie nous permettra d'aborder une classe liant propriétés, fonctions et méthodes.
I-F. Prérequis▲
L'utilisation et la compréhension de ce cours requièrent une bonne connaissance de base en VB ou VBA.
Vous devez notamment savoir déclarer et utiliser des variables et comprendre la notion de portée d'utilisation d'une variable.
Vous devez savoir déclarer et utiliser des fonctions personnalisées et des procédures requérant l'emploi de paramètres.
Il est également préférable que l'environnement de développement de VB ou VBE (Visual Basic Editor) vous soit familier, notamment les possibilités de débogage du code.
II. Notions d'objet▲
II-A. Les objets de la vie courante▲
Dans la vie courante, nous utilisons quotidiennement des objets.
Nous les définissons par un ensemble de caractéristiques et, généralement, nous pouvons agir avec ces objets.
Ainsi, si je regarde une boite à chaussures, je peux la définir.
Elle est noire, elle fait 40x18x12cm…
Je peux l'ouvrir, la fermer, la remplir, la vider…
Ma voiture possède quatre roues, un volant, un moteur de telle cylindrée.
Je peux la faire avancer, la stopper…
Dans ces exemples, nous discernons que nos objets possèdent des caractéristiques qui leur sont propres, et que nous pouvons définir des actions réalisables sur ou avec ces objets.
II-B. Les objets informatiques▲
Un objet informatique est similaire à un objet de la vie courante, et nous en utilisons quotidiennement dans nos codes VBA.
Ces objets possèdent des caractéristiques qui leur sont propres et nous pouvons agir sur ou avec ces objets.
Les caractéristiques d'un objet informatique sont appelées les propriétés de l'objet.
Les actions que nous pouvons réaliser sur ou grâce à ces objets sont appelées des méthodes.
Lorsque nous souhaitons utiliser un objet, nous devons donc connaître les propriétés de l'objet mises à notre disposition, et nous devons connaître aussi les méthodes utilisables sur cet objet.
II-B-1. Quelques objets informatiques▲
En VB6, nous utilisons tout le temps des objets. Tous les contrôles que nous plaçons sur un formulaire (qui est lui-même un objet) sont des objets.
En VBA pour Excel, nous manipulons les objets Range (plage de cellules), Worksheet (feuille de calcul), Workbook (classeur)…
En VBA pour Word, nous manipulerons les objets Word, Document…
En VBA pour Access, nous manipulerons les objets Form, Database, Recordset
En VB6 ou VBA, nous référençons parfois des bibliothèques d'objets externes, pour manipuler des objets particuliers. Par exemple, l'utilisation de la bibliothèque Microsoft Scripting Runtime permet de manipuler des objets fichier, dossier… non utilisables par défaut dans VB6 ou VBA.
II-B-2. L'explorateur d'objets en VB et VBA▲
L'explorateur d'objets permet… d'explorer les objets, c'est-à-dire d'en afficher les propriétés, méthodes et événements.
En VB 6.0 comme en VBA, l'affichage de l'explorateur d'objets est possible via le menu Affichage/Explorateur d'objets ou par le raccourci F2.
Lorsque l'explorateur d'objets est affiché, on peut alors choisir d'afficher les objets de toutes les bibliothèques disponibles du projet, ou de restreindre la liste des objets à une bibliothèque particulière.
On peut alors choisir l'objet pour lequel on souhaite afficher les propriétés, méthodes et événements.
III. Utilisation de variables simples, puis d'un « pseudoobjet », le type personnalisé▲
Lorsque l'on doit utiliser un « objet » informatique, on a trois possibilités. La première consiste à créer autant de variables que de propriétés.
La seconde utilise un type personnalisé.
La troisième, enfin, passe par l'utilisation d'une classe personnalisée.
Avant d'aborder la création d'une classe personnalisée, je vais brièvement exposer les deux premières méthodes, ne serait-ce que pour démontrer la puissance d'une classe par rapport aux variables simples et aux types personnalisés.
Pour ceux qui connaissent l'histoire des trois petits cochons, on peut imaginer la première solution comme étant la maison de paille, le type personnalisé étant la maison de bois, la classe personnalisée comme la maison de briques…
III-A. Manipulation des données d'un contact sans utilisation d'un type personnalisé▲
Si je souhaite manipuler les données d'un contact, je dois déclarer autant de variables que j'ai de données (champs) pour mon contact, puis leur attribuer les données de mon contact, et enfin les utiliser, par exemple au sein d'une procédure qui affiche un message à l'écran.
Sub
UtiliserContact
(
)
Dim
ContactNom As
String
Dim
ContactPrenom As
String
Dim
ContactAdresse As
String
Dim
ContactCP As
String
Dim
ContactLocalite As
String
Dim
ContactDateNaissance As
Date
ContactNom =
"Fauconnier"
ContactPrenom =
"Pierre"
ContactAdresse =
"20, Jevoumont"
ContactCP =
"4910"
ContactLocalite =
"Theux"
ContactDateNaissance =
DateSerial
(
1966
, 12
, 26
)
AfficherMessageContact ContactNom, ContactPrenom, ContactAdresse, _
ContactCP, ContactLocalite, ContactDateNaissance
End
Sub
Sub
AfficherMessageContact
(
Nom As
String
, Prenom As
String
, Adresse As
String
, _
CP As
String
, Localite As
String
, DateNaissance As
Date
)
MsgBox
"Bonjour "
&
Prenom &
" "
&
Nom &
"."
&
vbCrLf
&
_
"Vous habitez "
&
Adresse &
" à "
&
CP &
" "
&
Localite &
"."
&
vbCrLf
&
_
"Vous êtes né le "
&
Format
(
DateNaissance, "dddd dd mmmm yyyy"
), vbOKOnly
, "Salutations"
End
Sub
Ce qui me donnera le résultat suivant à l'écran.
III-A-1. Limites de l'utilisation de variables simples▲
Les limites du code présenté ci-dessus sont évidentes.
Chaque fois que je souhaite utiliser les données d'un autre contact, je dois réinitialiser mes variables, avec le risque d'en oublier, et donc de mélanger les données de mes contacts.
De plus, passer les variables à une procédure ou une fonction qui doit les utiliser n'est déjà pas pratique avec six variables…
...
AfficherMessageContact ContactNom, ContactPrenom, ContactAdresse, _
ContactCP, ContactLocalite, ContactDateNaissance
...
Sub
AfficherMessageContact
(
Nom As
String
, Prenom As
String
, Adresse As
String
, _
...
End
Sub
Imaginez ce que cela donnerait avec un objet défini par vingt ou trente champs…
Enfin, si je veux utiliser simultanément les données de plusieurs contacts, je vais devoir créer autant de variables ContactNom1, ContactNom2… que j'ai de contacts à gérer simultanément. Mission impossible !!
Je pourrais bien entendu également utiliser un tableau indexé pour gérer les données de x contacts, mais ce ne serait guère pratique pour les manipuler.
Dim
Contacts
(
9
, 5
)
Contacts
(
0
, 0
) =
"Fauconnier"
Contacts
(
0
, 1
) =
"Pierre"
...
...
Contacts
(
1
, 0
) =
"Dupont"
Contacts
(
1
, 1
) =
"Alain"
III-B. Qu'est-ce qu'une variable de type personnalisé (structure) et quand créer un type personnalisé?▲
Une variable de type personnalisé, appelé aussi structure, est un groupe de plusieurs variables simples dont le type personnalisé concrétise la structure.
Un type personnalisé sera créé lorsqu'il s'avérera nécessaire de manipuler plusieurs variables comme si elles représentaient une entité.
L'utilisation de variables de type personnalisé répondra partiellement aux objections émises plus haut quant à l'utilisation de variables simples.
III-C. Création d'un type personnalisé▲
Créer un type personnalisé de variable, c'est définir la structure de la variable, préciser quelles sont les variables simples qui seront groupées.
Les variables utilisées plus haut dans le cours constituent la structure de mon type personnalisé.
Je vais les réutiliser en les modifiant légèrement et créer, ou plutôt définir mon type personnalisé avec le code suivant :
Public
Type
tpContact
Nom As
String
Prenom As
String
Adresse As
String
CP As
String
Localite As
String
DateNaissance As
Date
End
Type
Remarquez qu'ici, j'ai supprimé le préfixe Contact pour mes variables, puisqu'elles sont définies à l'intérieur de mon type personnalisé.
Ce code se place toujours en tête de module standard. Il ne peut jamais être utilisé dans le corps d'une procédure ou d'une fonction.
III-D. Utilisation de mon type personnalisé▲
Je peux maintenant utiliser ce type personnalisé dans mon code
Sub
UtiliserContact
(
)
Dim
Contact As
tpContact
With
Contact
.Nom
=
"Fauconnier"
.Prenom
=
"Pierre"
.Adresse
=
"20, Jevoumont"
.CP
=
"4910"
.Localite
=
"Theux"
.DateNaissance
=
DateSerial
(
1966
, 12
, 26
)
End
With
AfficherMessageContact Contact
End
Sub
Sub
AfficherMessageContact
(
ByRef
Contact As
tpContact)
With
Contact
MsgBox
"Bonjour "
&
.Prenom
&
" "
&
.Nom
&
"."
&
vbCrLf
&
_
"Vous habitez "
&
.Adresse
&
" à "
&
.CP
&
" "
&
.Localite
&
"."
&
vbCrLf
&
_
"Vous êtes né le "
&
Format
(
.DateNaissance
, "dddd dd mmmm yyyy"
), vbOKOnly
, "Salutations"
End
With
End
Sub
Ce qui me donne le même résultat que tout à l'heure.
III-E. Différences entre l'utilisation de variables et l'utilisation d'un type personnalisé▲
Après avoir créé mon type personnalisé, j'utilise une variable de ce type au travers de mon code, en étant certain d'emporter avec moi la structure complète de ma variable.
De plus, l'écriture et la lecture du code sont plus simples, notamment lorsque je dois passer ma variable en argument d'une fonction ou d'une procédure.
...
AfficherMessageContact Contact
End
Sub
Sub
AfficherMessageContact
(
Contact As
tpContact)
...
est plus simple à écrire et à lire que
...
AfficherMessageContact ContactNom, ContactPrenom, ContactAdresse, _
ContactCP, ContactLocalite, ContactDateNaissance
End
Sub
Sub
AfficherMessageContact
(
Nom As
String
, Prenom As
String
, Adresse As
String
, _
CP As
String
, Localite As
String
, DateNaissance As
Date
)
...
Enfin, la maintenance de mon code est simplifiée lorsque la structure de ma variable change.
En effet, si je dois ajouter un « champ » à mon type personnalisé, les modifications se limitent à l'affectation du nouveau champ et à la récupération de sa valeur. Les passages et récupérations d'un paramètre contact ne changent pas.
III-F. Limites de l'utilisation d'un type personnalisé▲
L'utilisation d'un type personnalisé ne permet pas le traitement des données à l'intérieur de sa structure.
La vérification et le traitement des données sont délégués au code appelant, ce qui limite considérablement la portabilité du type.
En effet, dans l'exemple de l'utilisation de ma variable Contact, je dois déléguer au code appelant la vérification de date de naissance. Nulle part dans la structure Type… End Type, je n'ai la possibilité d'effectuer des actions relatives à la saisie d'informations.
De plus, la gestion des erreurs est également déléguée au code appelant.
Enfin, je ne peux gérer aucun événement relatif à un type personnalisé.
III-G. Gestion de plusieurs données s'appuyant sur une structure▲
Pour gérer plusieurs contacts, je peux bien entendu déclarer autant de variables de type tpContact que j'ai de contacts à manipuler, mais cette technique n'est pas très pratique.
Dim
Contact1 As
tpContact
Dim
contact2 As
tpContact
Dans ce cas, je pourrais également utiliser un tableau, ce qui simplifierait déjà l'utilisation et la manipulation de plusieurs contacts.
Dim
Contacts
(
9
) As
tpContact
With
Contacts
(
0
)
.Nom
=
"Fauconnier"
.Prenom
=
"Pierre"
End
With
With
Contacts
(
1
)
.Nom
=
"Dupont"
.Prenom
=
"Alain"
End
With
IV. Une classe pour gérer des contacts▲
L'utilisation d'une classe personnalisée va balayer ces restrictions et me donner la possibilité de mettre à disposition du code utilisateur de ma classe des propriétés, méthodes, erreurs et événements.
Dans la suite du cours, nous allons avancer petit à petit dans la création d'une classe permettant de gérer des contacts.
La classe que nous allons créer dans un premier temps ne sera vraiment pas professionnelle, mais nous améliorerons le code petit à petit pour intégrer calmement les notions essentielles à la création de classes sûres et portables.
IV-A. Notion de classe,notion d'objet, notion d'encapsulation▲
IV-A-1. Notion de classe▲
La classe représente la structure d'un objet. En cela, elle est comparable aux lignes de définition Type… End Type.
IV-A-2. Notion d'objet▲
Un objet est une variable dont le type s'appuie sur une classe. Que ce soit en VB ou en VBA, cette classe peut être personnalisée ou mise à disposition par l'application.
VB 6.0 comme VBA permettent d'utiliser des références externes. Dans VB 6.0, le menu Projet/Références… permet d'ajouter une référence. Dans VBA, c'est via le menu Outils/Références… que vous avez la possibilité d'ajouter une référence externe.
Ainsi, en Excel, Dim MaFeuille As Worksheet créera un objet appelé MaFeuille qui s'appuiera sur la classe qui définit les objets de type WorkSheet, c'est-à-dire les feuilles de calcul.
IV-A-3. Notion d'encapsulation▲
Nous devrons toujours veiller, lorsque nous construisons une classe, à ce qu'elle soit « encapsulée ».
Cela veut dire qu'à aucun moment, notre classe ne doit avoir besoin de données externes pour fonctionner. Dans les faits, cela veut dire que notre classe ne peut jamais faire appel à une variable extérieure à la portée du module de classe.
De même, une classe ne devrait jamais donner la main à l'utilisateur via par exemple un msgbox ou un inputbox. Il faudra donc développer à l'intérieur du module de classe des événements et des gestions d'erreurs qui rendront la main au code appelant.
Ces notions seront vues dans la suite du cours.
IV-B. Création de notre première classe▲
La classe que nous allons créer va nous permettre de manipuler un objet contact.
IV-B-1. Module de classe▲
IV-B-1-a. Ajout d'un module de classe en VB 6.0▲
En VB 6.0, nous créerons une classe personnalisée en ajoutant un module de classe via le menu Projet/Ajouter un module de classe
Nous choisissons alors d'ajouter le module de classe parmi les différentes icônes proposées.
Le nouveau module de classe apparaît dans l'arborescence de notre projet, sous le nœud Modules de classe
La première chose que nous allons faire, c'est renommer le module, car c'est lui qui déterminera le nom de notre classe.
Ce nom sera donc choisi avec soin, en évitant notamment les noms réservés par VB 6.0.
IV-B-1-b. Ajout d'un module de classe en VBA▲
En VBA, nous créerons une classe personnalisée par l'ajout d'un module de classe au sein de notre projet.
Les illustrations proviennent d'un VBA pour EXCEL, mais vous pouvez les transposer sans problème sur Word, PowerPoint, Access, Outlook…
Pour ajouter un module de classe, utilisons le menu Insertion/Module de classe.
Nous pouvons aussi utiliser le bouton d'outil d'ajout de module.
Un nouveau module apparaît dans l'arborescence du projet, dans le nœud Modules de classe.
La première chose que nous allons faire, c'est renommer le module, car c'est lui qui déterminera le nom de notre classe.
Ce nom sera donc choisi avec soin, en évitant notamment les noms déjà utilisés par VBA.
IV-B-2. Création de propriétés simples pour notre classe▲
Ce que nous allons voir maintenant est à proscrire en utilisation professionnelle de classes personnalisées.
La seule raison de leur mention ici est de permettre une prise en main « pas à pas » des notions nécessaires à la réalisation d'une classe performante.
Pour ajouter des propriétés simples à notre classe, il nous suffit de déclarer des variables publiques au sein de notre module de classe.
Pour cela, nous pouvons reprendre la structure de notre type personnalisé.
Le code est le suivant :
Public
Nom As
String
Public
Prenom As
String
Public
Adresse As
String
Public
CP As
String
Public
Localite As
String
Public
DateNaissance As
Date
Public
Sexe As
String
Ces variables publiques, parce que déclarées au sein d'un module de classe, sont des propriétés en lecture/écriture pour les objets qui s'appuieront sur notre classe.
Le terme variable publique est utilisé ici abusivement. La déclaration de variables via Public … dans un module de classe ne crée pas des variables publiques utilisables partout dans le projet, mais bien des propriétés publiques mises à disposition du code appelant par la classe dont l'objet est issu.
À l'extérieur du module de classe, ces propriétés devront être préfixées du nom de l'objet issu de la classe personnalisée.
IV-B-3. Utilisation de notre classe au sein d'un code▲
Pour utiliser un objet de type cContact au sein d'un code, nous utiliserons la même technique que pour notre type personnalisé.
En reprenant le code utilisé plus haut, nous avons deux modifications à réaliser pour utiliser notre classe plutôt que notre type personnalisé.
Voici le code :
Sub
UtiliserContact
(
)
Dim
Contact As
New
cContact
With
Contact
.Nom
=
"Fauconnier"
.Prenom
=
"Pierre"
.Adresse
=
"20, Jevoumont"
.CP
=
"4910"
.Localite
=
"Theux"
.DateNaissance
=
DateSerial
(
1966
, 12
, 26
)
.Sexe
=
"masculin"
End
With
AfficherMessageContact Contact
End
Sub
Sub
AfficherMessageContact
(
Contact As
cContact)
With
Contact
MsgBox
"Bonjour "
&
.Prenom
&
" "
&
.Nom
&
"."
&
vbCrLf
&
_
"Vous habitez "
&
.Adresse
&
" à "
&
.CP
&
" "
&
.Localite
&
"."
&
vbCrLf
&
_
"Vous êtes né le "
&
Format
(
.DateNaissance
, "dddd dd mmmm yyyy"
) &
"."
, vbOKOnly
, "Salutations"
End
With
End
Sub
La saisie semi-automatique permet, ici aussi, d'utiliser la liste des objets disponibles en fonction de la saisie.
Et voici les modifications à réaliser :
Le résultat est toujours le même, à savoir l'affichage d'un message de bienvenue.
Attention ! Bien que nous ayons déclaré nos variables publiques, il n'est pas possible de les utiliser telles quelles ailleurs que dans notre module de classe.
Ainsi, le code Debug.Print DateNaissance à l'extérieur de notre code provoquera une erreur de compilation.
IV-B-4. Conclusions▲
Nous venons de voir comment créer notre première classe.
Il n'y a pas de quoi fouetter un chat. Nous ne savons pas mieux contrôler les données avec ce code qu'avec l'utilisation d'un type personnalisé.
Néanmoins, nous avons vu comment créer un module de classe, lui affecter un nom qui sera celui de la classe que nous créons, et nous avons ajouté des variables « publiques » qui sont des propriétés en lecture/écriture disponibles pour les objets de notre classe.
IV-C. Création de vraies propriétés▲
Déclarer des variables publiques au sein d'un module de classe est certes rapide, mais peu intéressant.
Pour pouvoir manipuler, contrôler et travailler avec les propriétés d'un objet, nous allons déclarer des … propriétés.
IV-C-1. Notions de propriétés en lecture/écriture, en lecture seule et en écriture seule▲
IV-C-2. Propriétés en lecture/écriture▲
Une propriété en lecture/écriture est une propriété qui peut être lue ET modifiée.
Exemple: La propriété DateNaissance doit être en lecture pour pouvoir la récupérer et en écriture pour pouvoir la définir pour un contact particulier.
IV-C-3. Propriétés en lecture seule▲
Une propriété en lecture seule ne peut être ni définie ni modifiée.
Exemple: si je dote ma classe d'une propriété Age, elle sera en lecture seule, car elle dépendra de la propriété DateNaissance, qui sera, elle, en lecture/écriture.
IV-C-4. Propriétés en écriture seule▲
Une propriété pourrait être en écriture seule, et donc définissable, mais non lisible.
Je ne vois pas d'exemple concret pour ce cas.
IV-C-5. Propriétés en lecture/écriture qui basculent en lecture seule▲
Certaines propriétés doivent être définies une fois, puis être verrouillées pour ne plus pouvoir être modifiées.
Elles sont dès lors en lecture/écriture, mais le code les fera basculer en lecture seule pour éviter leur modification.
Exemple: Une propriété ID qui doit être définie, mais qui ne peut par la suite être modifiée. C'est le cas des clés primaires, pour ceux qui connaissent.
Nous verrons ce cas particulier dans la suite du cours.
IV-C-6. Utilisation de Property Get et Property Let▲
Property Get et Property Let permettent la création de propriétés au sein de la classe.
Property Get permet la lecture d'une propriété, Property Let permet l'écriture d'une propriété.
IV-C-6-a. La propriété « Nom » en lecture-écriture▲
La création d'une propriété passe par deux étapes : la définition d'une variable privée, et la création des blocs Property Get et Property Let.
Modifions le code de notre classe en modifiant la ligne
Public
Nom As
String
pour définir une variable privée
Private
mNom As
String
Remarquez que ma variable a changé de nom, par l'ajout d'un préfixe « m » précisant la limite de sa portée au module.
Cette variable ne sera donc pas manipulable par le code appelant qui utilisera notre objet.
La définition du nom de notre contact s'effectuera via le code
Property
Get
Nom
(
) As
String
' Propriété en lecture
Nom =
mNom
End
Property
Property
Let
Nom
(
Nom As
String
)
' Propriété en écriture
mNom =
Nom
End
Property
Notons que ces modifications n'ajoutent actuellement rien à notre classe par rapport à l'utilisation d'une variable déclarée avec Public. Patience…
Notez aussi que les propriétés ne sont pas déclarées Public, car elles le sont d'office. C'est leur raison d'être !
Notons cependant que des propriétés peuvent être déclarées Private pour n'être utilisables qu'à l'intérieur du module de classe.
IV-C-6-a-i. Fonctionnement du code▲
Pour nous aider à comprendre comment le code fonctionne avec les propriétés d'une classe, nous allons démarrer le code appelant vu plus haut avec la méthode « pas à pas », en mettant un point d'arrêt sur la ligne With Contact de la procédure UtiliserConctact
Puis nous démarrons le code. L'exécution s'arrête sur With contact. Nous sommes dans le module standard qui contient la procédure UtiliserContact.
Nous pressons sur F8 pour passer à la ligne suivante, puis encore sur F8. Le contrôle du code passe dans le module de classe pour s'arrêter sur Property Let.
L'infobulle nous montre que le paramètre Nom possède la valeur « Fauconnier » passée par le module standard.
Nous connaissons déjà le fonctionnement des paramètres, puisqu'il est identique dans une propriété à celui d'une procédure ou d'une fonction dans VBA. Rien de nouveau sous le soleil de ce côté-là.
Pressons F8. Le code repasse dans le module standard, et pressons F8 jusqu'à nous arrêter sur la ligne End Property. L'infobulle nous montre que la valeur du paramètre a été passée à la variable privée mNom.
Pressons F8 plusieurs fois pour arriver sur la ligne MsgBox… de la procédure AfficherMessageContact
Puis pressons F8, pour constater que le code repasse dans le module de classe, et s'arrête sur la ligne Property Get Nom(). Le code va récupérer la valeur de la variable mNom
Pour la passer au paramètre Nom
Ce qui permettra l'affichage du nom au sein du MsgBox.
On comprend donc maintenant le fonctionnement et le parcours du code au sein des propriétés de la classe. Mais à quoi cela sert-il ?
IV-C-6-a-ii. Contrôle de la saisie du nom en vue de sa modification▲
L'intérêt de passer par Property Let pour définir le nom de notre contact, c'est que nous pouvons ajouter du code au sein de la propriété, ce qui était impossible avec une variable de type personnalisé ou une variable publique de notre module de classe.
Imaginons que nous voulons que les noms de nos contacts soient systématiquement en majuscules. Grâce à Property Let, nous n'aurons aucune difficulté à satisfaire à cette exigence.
Modifions le code comme suit :
Property
Let
Nom
(
Nom As
String
)
' Propriété en écriture
mNom =
UCase
(
Nom)
End
Property
En plaçant un point d'arrêt sur End Property de cette propriété,
Nous verrons, grâce à l'infobulle, que mNom contient le nom en majuscules.
À la lecture de la propriété, le paramètre Nom recevra la valeur de mNom pour afficher, cette fois, un message différent de ceux vus précédemment.
IV-C-6-a-iii. Adaptation du code: Transformation des variables en propriétés▲
Maintenant que nous avons compris l'intérêt d'utiliser des propriétés plutôt que des variables, nous pouvons modifier le code en transformant toutes les variables en propriétés.
Toutes ces propriétés sont en lecture/écriture.
Option
Explicit
Private
mNom As
String
Private
mPrenom As
String
Private
mAdresse As
String
Private
mCP As
String
Private
mLocalite As
String
Private
mDateNaissance As
Date
Private
mSexe As
String
Property
Get
Nom
(
) As
String
' Propriété en lecture
Nom =
mNom
End
Property
Property
Let
Nom
(
Nom As
String
)
' Propriété en écriture
mNom =
UCase
(
Nom)
End
Property
Property
Get
Prenom
(
) As
String
' Propriété en lecture
Prenom =
mPrenom
End
Property
Property
Let
Prenom
(
Prenom As
String
)
' Propriété en écriture
mPrenom =
UCase
(
Prenom)
End
Property
Property
Get
Adresse
(
) As
String
' Propriété en lecture
Adresse =
mAdresse
End
Property
Property
Let
Adresse
(
Adresse As
String
)
' Propriété en écriture
mAdresse =
UCase
(
Adresse)
End
Property
Property
Get
CP
(
) As
String
' Propriété en lecture
CP =
mCP
End
Property
Property
Let
CP
(
CP As
String
)
' Propriété en écriture
mCP =
UCase
(
CP)
End
Property
Property
Get
Localite
(
) As
String
' Propriété en lecture
Localite =
mLocalite
End
Property
Property
Let
Localite
(
Localite As
String
)
' Propriété en écriture
mLocalite =
UCase
(
Localite)
End
Property
Property
Get
DateNaissance
(
) As
Date
' Propriété en lecture
DateNaissance =
mDateNaissance
End
Property
Property
Let
DateNaissance
(
DateNaissance As
Date
)
' Propriété en écriture
mDateNaissance =
DateNaissance
End
Property
Property
Get
Sexe
(
) As
String
' Propriété en lecture
Sexe =
mSexe
End
Property
Property
Let
Sexe
(
Sexe As
String
)
' Propriété en écriture
mSexe =
UCase
(
Sexe)
End
Property
Remarquez qu'il faut une similitude de type entre une propriété en lecture et la même propriété en écriture.
Ainsi, si la propriété en lecture est typée String, la propriété en écriture doit recevoir un paramètre typé String.
IV-C-6-a-iv. La propriété « Age » en lecture seule▲
Après avoir vu comment ajouter du code dans un Property Let, nous allons aborder une propriété en lecture seule avec du code pour calculer la valeur de la propriété.
Pour éviter que le programmeur du code appelant doive créer une fonction qui calcule l'âge du contact par rapport à la date de naissance, nous allons créer une propriété Age au sein de notre classe. Le code appelant devra alors seulement lire la propriété Age de l'objet contact, sans se soucier de l'algorithme utilisé.
Voici le code permettant de calculer l'âge révolu du contact.
Property
Get
Age
(
) As
Integer
Age =
Int
((
Date
-
mDateNaissance) /
365
)
End
Property
Il existe une multitude de façons de calculer l'âge, avec ou sans la fonction DateDiff. Ce cours ayant pour objectif l'apprentissage des classes personnalisées et non les algorithmes de calcul d'âge, le lecteur acceptera ma façon de calculer et la considérera pour ce qu'elle est, à savoir l'illustration d'une création de propriété en lecture seule.
Ainsi, le code suivant, placé dans un module standard, permettra de connaître l'âge d'un contact en fonction de sa date de naissance.
Sub
CreerContactAge
(
)
Dim
Contact As
New
cContact
Contact.Prenom
=
"Pierre"
Contact.DateNaissance
=
DateSerial
(
1966
, 12
, 26
)
MsgBox
Contact.Prenom
&
" a "
&
Contact.Age
&
" ans"
End
Sub
Ce qui affichera le message suivant
Dans ce cas précis, mDateNaissance étant une date, le fait de ne pas spécifier la date de naissance pose simplement un problème de calcul, et le résultat est erroné.
Il n'y a pas d'erreur amenant à un débogage ou un plantage, mais il faudra être vigilant et envisager tous les cas possibles.
Nous verrons très bientôt comment gérer ce type de situation.
IV-C-6-a-v. Un petit mot sur l'explorateur d'objets▲
J'ai abordé plus haut dans le cours l'explorateur d'objets. Cet explorateur permet également de parcourir les objets que nous créons.
Si je demande à l'explorateur d'objets de m'afficher les propriétés de ma classe cContacts, j'obtiendrai l'affichage suivant :
En sélectionnant une valeur dans la liste, j'obtiens les spécifications de cette propriété ou méthode
IV-C-6-a-vi. Conclusions▲
Nous venons d'approcher la puissance des propriétés de notre classe, car seule notre imagination va nous bloquer.
Si, par exemple, vous devez souvent calculer des emprunts, les taux de remboursement…, vous pouvez vous définir une classe cEmprunts et créer des méthodes et des propriétés pour cette classe.
Vous mettez alors à disposition du code, voire d'autres programmeurs, une classe qui peut être utilisée, même par quelqu'un qui ne connaît aucune formule de calcul…
Dans la suite du cours, nous allons enrichir notre classe avec des créations d'erreurs et des déclenchements d'événements.
Nous donnerons également au code appelant les moyens de contrôler l'exécution du code à l'intérieur de la classe. Mais… Patience !
IV-D. Déclenchement d'erreurs au sein de notre classe▲
Lorsque nous avons créé la propriété Age, nous avons remarqué que l'âge pouvait être erroné si la date de naissance n'était pas spécifiée.
Nous pourrions aussi avoir des surprises désagréables si nous créons un contact pour une personne qui n'est pas encore née, en introduisant une date ultérieure à la date système. Gênant…
Nous allons donc contrôler, dans Property Let DateNaissance, que la date passée en paramètre est valide.
Cela se fera très facilement avec le code suivant
Property
Let
DateNaissance
(
DateNaissance As
Date
)
' Propriété en écriture
If
DateNaissance <=
Date
Then
mDateNaissance =
DateNaissance
Else
...
End
If
End
Property
Jusque là, ce n'est pas trop compliqué. Mais par quoi faut-il remplacer les … ? Par ceci ?
Property
Let
DateNaissance
(
DateNaissance As
Date
)
' Propriété en écriture
If
DateNaissance <=
Date
Then
mDateNaissance =
DateNaissance
Else
MsgBox
"Date non valide"
, vbOKOnly
+
vbExclamation
, "Erreur de saisie"
End
If
End
Property
En effet, si je passe comme date de naissance une date ultérieure à la date du jour, j'ai un beau message d'erreur.
Mais, ce faisant, j'enfreins la règle d'encapsulation vue plus haut, car ma classe passe outre le code appelant pour s'adresser directement à l'utilisateur derrière son écran, sans donner la possibilité au code appelant de reprendre la main.
Une classe doit TOUJOURS, en cas d'erreur, rendre la main au code appelant et JAMAIS à l'utilisateur.
C'est au code appelant qu'il revient de gérer l'erreur survenue dans la classe.
Nous allons donc devoir créer du code au sein de la classe pour rendre la main au code appelant et lui déléguer la responsabilité de traiter l'erreur.
La classe a la responsabilité de signaler l'erreur, le code appelant a la responsabilité de le gérer.
IV-D-1. Génération d'une erreur au sein de la classe▲
Pour informer le code appelant d'une erreur, dans le cas présent une erreur de saisie, nous allons générer une erreur avec Err.Raise Number, [Source], [Description]…
Property
Let
DateNaissance
(
DateNaissance As
Date
)
' Propriété en écriture
If
DateNaissance <=
Date
Then
mDateNaissance =
DateNaissance
Else
Err
.Raise
Number:=
vbObjectError
+
1
, Description:=
"Date non valide"
End
If
End
Property
Pour générer l'erreur, nous avons besoin d'un numéro d'erreur, et optionnellement d'une source et d'une description de l'erreur. Nous pouvons également définir des paramètres utiles lors de l'utilisation de fichier d'aide, mais je n'en parlerai pas ici.
Certaines plages de numéros d'erreur étant réservées par Windows et VBA, nous utiliserons la constante vbobjecterror en lui ajoutant des unités, et il faudra, pour le support technique, tenir des fiches à jour avec le numéro de l'erreur et les lignes de code concernées, pour faciliter la maintenance de code.
En plus de ce numéro d'erreur, nous utiliserons le paramètre Description qui explicitera l'erreur rencontrée.
Si, après cette modification de la propriété, nous relançons le code, nous avons un message d'erreur, mais au niveau du code appelant et plus au niveau de l'utilisateur « final ».
On comprend donc clairement ici que la main est rendue au code appelant. À lui la responsabilité de la gestion de l'erreur, par exemple avec le code suivant
On
Error
GoTo
Erreurs
Contact.Prenom
=
"Pierre"
Contact.DateNaissance
=
DateSerial
(
2008
, 12
, 26
)
MsgBox
Contact.Prenom
&
" a "
&
Contact.Age
&
" ans"
Erreurs
:
Select
Case
Err
Case
-
2147221503
MsgBox
"La date saisie n'est pas valide et ne sera pas enregistrée"
, vbOKOnly
+
vbExclamation
, "Erreur de saisie"
Resume
Next
End
Select
C'est donc bien le code appelant qui reprend la main et qui décide de la façon de traiter l'erreur.
IV-E. Mettre des événements à disposition du code appelant▲
VB 6.0 et VBA permettent de créer des événements liés à une classe personnalisée. Ces événements sont levés lors de certaines manipulations à l'intérieur de la classe et passent la main au code appelant qui peut choisir de les gérer ou pas
IV-E-1. Exemples d'événements mis à disposition par des classes d'objets▲
Nous connaissons tous la gestion des événements pour des objets couramment utilisés lors d'une programmation en VB 6.0 ou en VBA.
Voici par exemple la gestion de l'événement de chargement d'un « form » en VB 6.0
… et voici l'illustration de la gestion de l'événement « SelectionChange » d'une feuille de calcul Excel, recevant en paramètre la cellule active de la plage sélectionnée.
Les événements qu'il est possible de gérer dépendent évidemment de l'objet que l'on manipule. Ainsi, dans une classe personnalisée, il n'y a par défaut aucun événement à la disposition du code qui exploite un objet issu de cette classe. C'est au concepteur de la classe qu'il revient de créer les événements qu'il souhaite mettre à disposition du code utilisateur.
IV-E-2. Création d'un événement pour notre classe personnalisée▲
La mise à disposition d'événements pour des objets d'une classe personnalisée n'impose pas la gestion de ces événements par le code appelant. Dans Excel, lorsque vous manipulez des feuilles de calcul, vous n'êtes pas tenus de gérer les événements mis à votre disposition. Il en est de même pour les événements de vos classes personnalisées, qui ne seront peut-être jamais gérés par le code appelant. C'est ce qui différencie les événements des erreurs, qui sont, quant à elles, levées systématiquement, qu'elles soient gérées ou non par le code appelant.
IV-E-2-a. Création et gestion d'un événement sans paramètre▲
Imaginons que nous souhaitons que le code appelant puisse gérer le fait que l'anniversaire du contact surviendra dans les 10 jours, et que nous souhaitons déclencher l'événement lors de la saisie de sa date de naissance.
IV-E-2-a-i. Création de l'événement▲
Tout d'abord, il faut déclarer l'événement. Celui-ci se déclare avant la première méthode, la première propriété ou la première fonction, tout en haut du module. Créons un événement appelé AnniversaireDans10Jours. Le code utilisé est simple
Public
Event AnniversaireDans10jours
(
)
Lorsque cela est fait, il nous reste à déclencher l'événement lorsque la condition est remplie. Pour cela, nous allons modifier le code de property let DateNaissance, de façon à tester la date d'anniversaire
Property
Let
DateNaissance
(
DateNaissance As
Date
)
' Propriété en écriture
Dim
DateAnniversaire As
Date
mDateNaissance =
DateNaissance
' Calcul de la date anniversaire pour l'année en cours
DateAnniversaire =
DateSerial
(
Year
(
Date
), Month
(
DateNaissance), Day
(
DateNaissance))
If
DateAnniversaire >=
Date
And
DateAnniversaire <=
Date
+
10
Then
RaiseEvent AnniversaireDans10jours
End
Property
Avec la saisie semi-automatique, la liste des événements disponibles pour l'objet apparaît après la saisie de RaiseEvent
Nous venons donc de créer un événement pour les objets de notre classe. Si cet événement est géré par le code appelant, ce dernier pourra, par exemple, avertir l'utilisateur que le contact qu'il est en train de gérer a son anniversaire dans les dix prochains jours.
IV-E-2-a-ii. Gestion de l'événement dans le code appelant▲
Pour pouvoir gérer l'événement de l'objet issu de notre classe cContact, nous devons déclarer à VB 6.0 ou VBA que nous voulons gérer les événements pour cet objet. Pour cela, il suffit de déclarer notre objet de la façon suivante, en utilisant Dim ou Public en fonction de la portée d'utilisation voulue pour notre objet.
Dim
WithEvents oContact As
cContact
Tant en VB 6.0 qu'en VBA, les modules standard ne permettent pas de gérer des événements pour des objets, qu'ils soient personnalisés ou non. Il n'est donc pas possible de déclarer des objets avec la clause WithEvents au sein d'un module standard. La ligne est directement refusée et mise en rouge.
Pour info, la déclaration avec WithEvents d'un objet qui ne possède pas d'événements ne déclenche pas d'erreur lors de la saisie du code, mais déclenche une erreur à l'exécution. Il n'est donc possible d'utiliser WithEvents que pour des objets pour lesquels il existe des événements gérables.
Pour illustrer cela, nous allons utiliser un formulaire avec un bouton.
Dans un formulaire de notre projet (formulaire pour VB 6.0 ou userform pour VBA), déclarons un objet de type cContact en tête de module.
Option
Explicit
Dim
WithEvents oContact As
cContact
En déclarant notre objet de cette manière, nous pouvons gérer les événements qui lui sont attachés. En effet, dans la fenêtre du code, nous disposons de l'objet dans la liste déroulante de gauche,
et de la liste de ses événements dans la liste déroulante de droite.
Lorsque l'objet a été déclaré ce cette manière, nous pouvons gérer l'événement souhaité
Private
Sub
oContact_AnniversaireDans10jours
(
)
MsgBox
"L'anniversaire de "
&
oContact.Prenom
&
" a lieu dans les 10 prochains jours"
End
Sub
Si, sur le formulaire (VB 6.0) ou le userform (VBA), nous plaçons un bouton nommé btnContact, nous pouvons gérer notre contact via un clic sur ce bouton, avec le code suivant
Private
Sub
btnContact_Click
(
)
Set
oContact =
New
cContact
With
oContact
.Prenom
=
"Manon"
.DateNaissance
=
DateSerial
(
1992
, 12
, 3
)
End
With
End
Sub
En démarrant le code, nous aurons le message suivant
Pour que l'exemple fonctionne, il faut évidemment que la date de naissance utilisée soit calculée pour que l'anniversaire tombe dans les 10 prochains jours par rapport à la date système de vote pc.
Si nous suivons l'exécution du code pas à pas, nous pouvons comprendre comment la levée de l'événement fonctionne. Pour cela, plaçons un point d'arrêt sur la ligne de code qui attribue la date de naissance.
Démarrons le code. L'exécution s'arrête sur la ligne qui attribue la date de naissance.
Avec F8, avançons pas à pas. L'exécution saute dans le module de classe, et nous pressons F8 jusqu'à la ligne qui teste la date de naissance
La date de naissance est testée. Si la condition est remplie, l'événement est généré
L'exécution saute dans le code appelant pour générer l'événement, et nous constatons que l'affichage du message va avoir lieu
Après affichage du message et clic sur le bouton OK, on passe à la fin de la gestion de l'événement dans le code appelant
Puis on retourne dans le code de la classe pour terminer le code lié à la propriété DateNaissance en écriture
Et on repasse dans le code appelant pour continuer l'exécution du code
Comme vous le voyez ici, la mise en place d'un événement au sein d'une classe est finalement assez simple et s'effectue en deux temps : déclarer un événement au niveau du module, puis générer l'événement dans le code, à l'endroit opportun. Il ne reste plus qu'à choisir de gérer ou non l'événement dans le code appelant.
IV-E-2-b. Création d'un événement qui passe un paramètre au code appelant▲
IV-E-2-b-i. Exemples d'événements avec paramètre(s)▲
Certains événements passent au code appelant un ou plusieurs paramètres. C'est le cas de l'événement Unload d'un form en VB 6.0, qui passe un paramètre
ou encore de l'événement BeforeDoubleClick d'une feuille de calcul Excel, qui passe quant à lui deux événements au code appelant.
Ces paramètres peuvent alors être gérés ou utilisés par le code appelant.
IV-E-2-b-ii. Création de l'événement au sein de la classe▲
Il est possible de passer un paramètre au code appelant. Ainsi, on pourrait vouloir passer la date de naissance au code appelant lorsque le contact a son anniversaire dans les 10 jours.
Pour cela, on doit modifier la déclaration de l'événement.
Il faut aussi modifier également le code qui génère l'événement, car il doit correspondre à la syntaxe de la déclaration de l'événement.
Bien entendu, le code qui gère l'événement doit lui aussi correspondre à la syntaxe de l'événement paramétré.
Le code appelant est bien entendu libre d'utiliser ou pas le paramètre passé lors de la levée de l'événement. Ici, le paramètre est utilisé dans un messagebox
IV-E-2-b-iii. Utilisation de l'événement et de son paramètre dans le code appelant▲
IV-E-2-c. Modification du déroulement du code dans la classe grâce à un paramètre « décisionnel »▲
IV-E-2-c-i. Création d'un paramètre décisionnel▲
Un paramètre décisionnel est un paramètre dont la modification par le code appelant va modifier l'exécution du code au sein de la classe. L'exemple typique de paramètre « décisionnel » est le paramètre Cancel utilisé par nombre de codes évènementiels.
Imaginons que nous voulions contrôler la modification de la propriété sexe de notre contact. On se rend compte rapidement que, sauf à devoir changer le contenu de la garde-robe du contact, il peut être utile de mettre à disposition du code appelant un événement lorsque celui-ci modifie la propriété Sexe du contact.
Nous allons déclarer l'événement dans la classe en utilisant le code suivant
Public
Event ModificationGenre
(
ByRef
Cancel As
Boolean
)
Remarquez que le paramètre Cancel est passé ByRef.
Il nous faut modifier la propriété Sexe en écriture, dans la classe cContact
Property
Let
Sexe
(
Sexe As
String
)
' Propriété en écriture
Dim
Cancel As
Boolean
If
mSexe <>
""
Then
RaiseEvent ModificationGenre
(
Cancel) ' Teste si le genre a déjà été attribué
If
Not
Cancel Then
mSexe =
UCase
(
Sexe) ' Si Cancel = FALSE, on n'annule pas la modification...et donc on modifie!
End
Property
Le code modifié testera si le genre a déjà été défini. Si oui, l'événement est généré, à charge pour le code appelant de définir la suite des opérations.
IV-E-2-c-ii. Gestion de l'événement dans le code appelant▲
Dans le code appelant (code du form ou du userform), on va gérer l'événement, puisque nous avons maintenant un deuxième événement dans la liste de droite.
Private Sub oContact_ModificationGenre(Cancel As Boolean)
Dim Reponse As VbMsgBoxResult
Reponse = MsgBox("Etes-vous certain de vouloir modifier le genre du contact?", vbYesNo + vbQuestion, "Gestion du contact")
If Reponse <> vbYes Then Cancel = True
End Sub
Remarquez que la ligne de déclaration de la procédure évènementielle reprend la structure de la déclaration de l'événement dans le module de classe,
et donc passe le(s) paramètre(s) défini(s) dans cette déclaration.
À nouveau, utilisons l'exécution pas à pas pour bien comprendre ce qui se passe. Pour cela, nous allons utiliser le code appelant suivant, dans la procédure évènementielle Click du bouton btnContact
Private
Sub
btnContact_Click
(
)
Set
oContact =
New
cContact
With
oContact
.Prenom
=
"Manon"
.Sexe
=
"F"
End
With
oContact.Sexe
=
"M"
MsgBox
oContact.Prenom
&
": sexe "
&
UCase
(
oContact.Sexe
)
End
Sub
Plaçons un point d'arrêt sur la ligne qui affecte le genre du contact
Démarrons l'exécution du code, qui s'arrête sur la ligne qui définit pour la première fois le genre du contact
Le code passe dans le module de classe, teste si mSexe est une chaine vide.
Comme c'est le cas, l'événement n'est pas généré, et le code passe à la ligne suivante, sur laquelle on va tester la variable locale Cancel, définie à False par défaut
Cancel est différent de True, donc la variable mSexe reçoit la valeur passée par le code appelant.
Et le code continue, et repasse dans le code appelant pour, cette fois, modifier le genre du contact, puisque nous venons de l'attribuer à la ligne précédente.
Le code appelant veut modifier la propriété Sexe du contact, le code repasse donc dans le module de classe sur la propriété Sexe en écriture et teste si mSexe est une chaine vide. Ce n'est plus le cas, puisque la valeur « F » a déjà été attribuée
Donc, l'événement est généré avec Cancel = False (valeur par défaut).
Le code repasse au code appelant puisqu'une procédure évènementielle a été rédigée. Cette procédure évènementielle reçoit bien le paramètre Cancel avec la valeur False
Si nous répondons NON à la question posée, la valeur de Cancel est modifiée
La procédure évènementielle se termine, et le code repasse dans le module de classe pour continuer son exécution. On voit ici que la valeur de Cancel a été modifiée, grâce au mot-clé ByRef utilisé lors de la déclaration de l'événement en début du module de classe.
Comme Cancel vaut True, mSexe n'est pas modifié. La modification de Cancel par le code appelant a donc bien modifié l'exécution du code à l'intérieur de la classe.
Si nous avions répondu Oui à la question posée par le code appelant, la valeur de Cancel n'aurait pas été modifiée, et donc mSexe aurait reçu la nouvelle valeur, ce qui aurait pour conséquence que notre contact féminin serait devenu un homme…
On comprend donc mieux ici, grâce à cet exemple, comment fonctionne le paramètre Cancel présent dans de nombreuses procédures évènementielles que nous rencontrons avec les objets que nous utilisons dans nos applications.
V. Conclusions▲
Cette partie du tutoriel nous a permis de comprendre ce qu'était un objet personnalisé. Nous avons abordé les notions de propriété, génération d'erreurs et génération d'événements
Nous voici arrivés au terme de la première partie de ce que je souhaite vous expliquer sur le fonctionnement des classes personnalisées.
Dans une seconde partie, nous envisagerons l'utilisation de cette classe « métier » au travers d'une classe qui liera notre contact à une source de données. Nous verrons que peu de choses seront à modifier dans cette nouvelle classe pour lier nos données à un classeur Excel ou à une base de données Access.
VI. Remerciements▲
Merci à jeannot45 et à bbil pour la relecture, et aux équipes de office et de vb sur DVP