Introduction
Ce tutoriel propose de comprendre les énumérations en programmation et leur périmètre d'intervention.
Il a été pensé en Visual Basic Application (VBA) et plus précisément sur Microsoft Excel.
Il est néanmoins adaptable en tous langages, ce ne sont que des principes algorithmiques.
Retour sur les constantes
Une des bonnes pratiques en matière de programmation est d'utiliser des constantes partout où l'on écrit une valeur dans le code.
L'instruction MsgBox "Fichier sauvegardé avec succès." sera avantageusement remplacée par :
Const cs_Message As String = "Fichier sauvegardé avec succès."
MsgBox cs_Message
Ainsi si le message change, le code fonctionne toujours principalement si cette valeur est utilisée à plusieurs endroits du programme et surtout on s'offre l'opportunité de stocker un jour ces valeurs dans un fichier de configuration.
Constantes multiples
Le besoin est fréquent d'avoir plusieurs constantes qui représentent le même élément :
Pour les jours de la semaine, j'ai besoin de 7 constantes.
Const cs_Lundi = 1
Const cs_Mardi = 2
Etc.
Je peux faire de même pour des mois, des directions (Nord - Sud - Est - Ouest), du matériel (Ordinateur - Imprimante - Clavier - etc.).
La gestion d'un tel système n'est pas toujours aisée à maintenir et faire fonctionner.
Les énumérations
Une énumération permet de regrouper des constantes de type entier et de même nature au même endroit.
Les déclarations de constantes précédentes peuvent s'écrire dans une énumération à l'aide d’une instruction « Enum ».
Enum ESemaine
eLundi
eMardi
eMercredi
eJeudi
...
End Enum
Portée
Une énumération peut être déclarée publique :
Public Enum ESemaine
Elle peut avoir une portée privée : Private Enum ESemaine
Ce n'est pas un type de données, mais une déclaration, elle ne peut être déclarée dans une procédure - fonction - propriété.
Usage
Déclaration
Comme les autres types de données, avant usage, il faut déclarer une variable de ce type :
Dim MonJour As ESemaine
Affectation
Les énumérations s’utilisent comme n'importe quelle variable, à ceci près que l'on commence par donner le nom de l'énumération suivi d’un point.
MonJour = ESemaine.
Intellisence
Quand on saisit le point, l'éditeur Visual Basic, nous propose les valeurs, que nous appellerons « données membres ».
Il ne reste qu’à choisir sa donnée.
MonJour = ESemaine.eJeudi
.
Il n'est pas obligatoire de préciser le nom de l'énumération sauf s’il existe une constante ou une donnée membre d'une autre énumération qui porte le même nom.
MonJour = eJeudi
' Possible s'il n'y a pas d'ambiguïtés.
MsgBox MonJour
' Affiche 3.
S'il est effectivement possible de passer par une variable, ce n'est pas obligatoire et on peut utiliser l'énumération tel-quel :
MsgBox ESemaine.eLundi
.
Définir les valeurs
Mais quelles valeurs peuvent bien afficher les données membres de l'énumération ?
Effectivement, à aucun moment nous n'avons défini ces valeurs.
Tout d'abord, c'est forcément un nombre de type entier (Byte - Integer - Long).
Visual Basic attribue des valeurs par défaut.
Par défaut, le premier membre de l'énumération prend la valeur zéro (0) :
ESemaine.eLundi = 0
.
Les autres membres sont incrémentés de 1 par rapport à la valeur précédente.
ESemaine.eMardi = 1
ESemaine.eMercredi = 2
ESemaine.eJeudi = 3
L'utilisateur peut fixer ses valeurs personnelles
Enum MyEnum
eConstA = 7
' Prend la valeur => 7
eConstB
' Prend la valeur précédente + 1 => 8
eConstC
' Prend la valeur précédente + 1 => 9
eConstD = 12
' Prend la valeur => 12
eConstE
' Prend la valeur précédente + 1 => 13
End Enum
Le second membre n'a pas de valeur fixée, il prend donc la valeur précédente incrémentée de 1 soit 8.
Pour le 3° membre c'est la même chose, et il vaut 9.
La 4° constante voit sa valeur attribuée par l'utilisateur et la 5° valeur est incrémentée de 1 (13).
Ce qui précède revient donc au même que d'écrire :
Enum MyEnum
eConstA = 7
eConstB = 8
eConstC = 9
eConstD = 12
eConstE = 13
End Enum
Valeurs identiques
Plusieurs constantes peuvent porter la même valeur.
Enum MyEnum
eConstA = 1
eConstB = 1
eConstC = 0
eConstD = 1
eConstE = 0
eConstF = 0
eConstG = 1
eConstH = 0
End Enum
Comme ici, nous pouvons créer des champs de bits, ou systèmes à deux états (ouvert/fermé).
Une constante peut en utiliser une autre
Une donnée de l'énumération peut fixer sa valeur à partir d'une autre tant que l'expression reste constante.
Enum MyEnum
eConstA = 7
' Prend la valeur => 7
eConstB
' Prend la valeur précédente + 1 => 8
eConstC = eConstA + 2
' Prend la valeur => 9
eConstD = 12
' Prend la valeur => 12
eConstE = eConstD + 3
' Prend la valeur => 15
End Enum
Ordre des données membres
Il n'y a aucune obligation à définir des valeurs en ordre croissant et l'énumération suivante est valide :
Enum MyEnum
eConstA = 12
' Prend la valeur => 12
eConstB
' Prend la valeur précédente + 1 => 13
eConstC = 7
' Prend la valeur => 7
End Enum
La valeur peut-être négative
Enum MyEnum
eConstA = -7
' Prend la valeur => -7
eConstB
' Prend la valeur précédente + 1 => -6
End Enum
Constante Double
Il est possible de renseigner la constante avec un type double, mais à l'usage, elle est convertie en entier.
Enum MyEnum
eConstA = 12.4
' Prend la valeur => 12
eConstB
' Prend la valeur précédente + 1 => 13
End Enum
Type de données d'une énumération
Les valeurs de l'énumération sont de type long.
Dim MonJour As ESemaine
MonJour = ESemaine.eLundi
Vérification du type de données
TypeName(MonJour)
' Retourne "Long".
VarType(MonJour)
' Retourne vbLong 3 Entier long
Visual Basic ne tient pas compte du type
Dim iJour As Integer, MonJour As ESemaine
MonJour = ESemaine.eLundi
La conversion d'un type énumération en un type entier est transparente.
iJour = MonJour
' Pas besoin de conversion de type.
Il est bien sûr possible de convertir explicitement.
iJour = CInt(MonJour)
Visual Basic ne respecte pas la casse
Comme pour les étiquettes Goto (contrairement à la plupart des instructions du langage), quand on appelle dans le code une constante de notre énumération, l'écrire en minuscule, modifiera la casse dans la déclaration.
Enum ESemaine
' Casse Majuscule
eLundi = 1
' Casse Majuscule
End Enum
Dim MonJour As esemaine
MonJour = esemaine.elundi
' Casse en minuscule, modifie la casse de la donnée membre dans la déclaration.
Enum ESemaine
' La casse reste en majuscule sur le nom de l’énumération.
elundi = 1
' La casse des données membres est modifiée.
End Enum
Associer une chaine
Lorsque l'on a des constantes numériques comme les jours de la semaine, immanquablement, nous souhaitons y associer un texte et dans les forums les questions à ce sujet sont nombreuses.
Enum ESemaine
eLundi = 1
eMardi
eMercredi
eJeudi
eVendredi
eSamedi
eDimanche
End Enum
Synchroniser le nom du jour
ESemaine.eLundi = "Lundi", ESemaine.eMardi = "Mardi", etc.
Structures conditionnelles (If/Case)
C'est sûrement la méthode la moins productive, mais selon les valeurs dans l'énumération, il n'y a guère de choix.
Dim monJour As ESemaine
MonJour = ESemaine.eMardi
Select Case MonJour
Case ESemaine.eLundi
' Ou Case 1
MsgBox "Lundi"
Case ESemaine.eMardi
MsgBox "Mardi"
End Select
Fonction Choose
Si les valeurs des constantes sont incrémentées de 1, alors c'est un jeu d'enfant d'associer un contenu.
MonJour = ESemaine.eJeudi
MsgBox Choose(MonJour, "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
Avec la variable "MonJour" qui prend la valeur "ESemaine.eJeudi" (soit la valeur 4), la fonction Choose() retournera "Jeudi".
L'indice de départ de la fonction « Choose » est 1.
Utiliser un Array
C'est le même principe que la fonction Choose() à la différence que dans un Array, l'indice commence à 0.
Dim LibJours As Variant
LibJours = Array("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
Dim monJour As ESemaine
MonJour = ESemaine.eJeudi
MsgBox LibJours(MonJour)
' Affiche "Jeudi" en considérant que l'énumération commence à 0.
Parcourir une énumération
Quand les valeurs des constantes s'incrémentent, une boucle permet de parcourir toutes les valeurs de l'énumération.
Dim MonJour As ESemaine, i As ESemaine
MonJour = ESemaine.eJeudi
For i = ESemaine.eLundi to ESemaine.eDimanche
If (i = MonJour) Then
If MonJour < ESemaine.eSamedi) Then
MsgBox "Pas de chance, aujourd'hui tu bosses !"
Exit For
End If
' MonJour < ESemaine.eSamedi
End If
' i = MonJour
Next i
Prendre une partie de l'énumération
Dim MonJour As ESemaine, i As ESemaine
MonJour = ESemaine.eJeudi
For i = ESemaine.eLundi to ESemaine.eVendredi
If (MonJour = i) Then
MsgBox "Pas de chance, aujourd'hui tu bosses !"
End If
' MonJour = i
Next i
Membres masqués
Comme vu plus haut, les membres de l'énumération sont visibles dans l'éditeur Visual Basic par l’intermédiaire d’Intellisense.
Il est possible de masquer certains membres en commençant leur nom par un underscore (_) et en l’entourant de crochets.
Enum ESemaine
[_First]
' Prend la valeur par défaut 0.
eLundi = 1
eMardi
eMercredi
eJeudi
...
[_Last]
' Dernier membre de l'énumération + 1 soit eDimanche + 1 = 8.
End Enum
Ce n'est pas parce qu’ils ne sont pas visibles dans l'éditeur, qu'ils ne sont pas utilisables sans quoi cela n'aurait aucun intérêt.
Si on connait leur existence, on peut les insérer dans son code.
C'est une technique éprouvée pour initier des bornes. C'est idéal pour parcourir l'énumération complète.
Dim i As ESemaine
For i = ESemaine.[_First] To ESemaine.[_Last]
...
Rôle d'une énumération
Considérons l'énumération suivante :
Enum UneEnum
eConstA = 12
eConstB = 3
eConstC = 577
eConstD = 602
eConstE = 17259
End Enum
Et la boucle suivante :
Dim myEnum As UneEnum, i As UneEnum
myEnum = UneEnum.eConstE
For i = UneEnum.eConstA To UneEnum.eConstE
If (i = UneEnum.eConstE) Then
' Faire quelque chose
End If ' i = UneEnum.eConstE
next i
Pour 5 constantes, soit 5 tours de boucle avec une énumération incrémentale, ici, il faut 17259 tours de boucles. C'est tout simplement inacceptable !
À remarquer en outre, que si on cherche UneEnum.eConstB, on ne le trouvera jamais en commençant une boucle à 12 !
Est-ce à dire qu'une énumération est une façon élégante d'organiser et documenter son programme et que l'on ne peut rien en faire d'autre ?
Dans ce sens oui, et si les valeurs ne sont pas issues d'une séquence identique d'une constante à l'autre, il existe d'autres façons d'arriver au même résultat.
Utiliser des Array
Je préfère dans ce cas utiliser un Array de constantes. C'est bien plus élégant, simple et efficace.
MyArray = (12, 3, 577, 602, 17259)
Les collections
Un peu lourd et surtout n'importe qui peut enlever, rajouter, modifier les données.
Piloter les données
Je viens de construire un Array, mais rien ne me dit à quoi correspond la valeur 577 de mon tableau.
Et si je faisais une énumération qui m'indique le rôle de chaque valeur ?
Enum EPopulation
[_First]
' -1
MaFamille = 0
MaMaison = 1
NosDeputes = 2
LeBourg = 3
LeVillage = 4
[_Last]
End Enum
Il devient facile d'aller chercher une valeur dans notre tableau en fonction de la valeur de la constante.
Dim myPop As EPopulation
myPop = EPopulation.LeBourg
MsgBox MyArray(myPop)
C'est ici qu'intervient le vrai rôle des énumérations, ce pour quoi elles ont été créées.
Il ne faut pas envisager une énumération comme un simple regroupement de constantes.
Dans le cas présent, elles jouent le rôle de « pointeur » vers des données. Elles se comportent comme un centre d'aiguillage.
D'autres usages
Les énumérations sont depuis le début de la programmation un élément important trop souvent ignoré des néophytes qui n'en saisissent pas toute la portée.
Les propriétés visibles dans l'inspecteur de l'éditeur Visual Basic sont issues d'énumérations.
Les combinaisons de boutons et options de la fonction MsgBox sont des énumérations.
Elles sont particulièrement indiquées pour conserver des bits.
Enum EChar
[_First]
Bit1 = 1
' Bit 1 = 1
Bit2 = 0
' Bit 2 = 2
Bit3 = 0
' Bit 3 = 4
Bit4 = 0
' Bit 4 = 8
Bit5 = 0
' Bit 5 = 16
Bit6 = 0
' Bit 6 = 32
Bit7 = 1
' Bit 7 = 64
Bit8 = 0
' Bit 8 = 128
[_Last]
End Enum
Ici, nous avons la valeur 65 qui est le code ASCII du caractère "A" majuscule.
Opérations et énumération
Il est possible d’additionner des constantes d'énumération, ce sont des valeurs comme les autres.
Dim WE As ESemaine
WE = ESemaine.eSamedi + ESemaine.eDimanche
Si Samedi vaut 6 et dimanche 7 alors la variable WE prendra la valeur 13.
Calculs ambigus
Soit une variable MesJours égale à ESemaine.eLundi + ESemaine.eMardi, ce qui donne le résultat 3 (1 + 2), or c'est aussi la valeur de ESemaine.eMercredi.
En pareil cas, calculer avec ces constantes devient hasardeux.
Utiliser une autre séquence
Est-ce à dire qu'il faut abandonner l'idée de calculer avec des énumérations ?
Avoir des constantes incrémentées de 1 c'est bien, mais peut-être qu'une autre suite est possible.
Il faudrait que quel que soit les combinaisons des membres de l'énumération, la somme de leurs valeurs ne donne jamais le même résultat.
Cette suite existe, il suffit que la valeur de la constante soit le double de la constante précédente et que la première commence à 1 comme dans l'énumération suivante :
Enum ESemaine
[_First]
eLundi = 1
eMardi = 2
eMercredi = 4
eJeudi = 8
eVendredi = 16
eSamedi = 32
eDimanche = 64
[_Last]
End Enum
Bien sûr cette suite n'est pas choisie au hasard. Avec elle, nous pouvons additionner n'importe quelle valeur, nous ne trouverons jamais deux fois le même résultat.
En reprenant l'exemple précédent ESemaine.eLundi + ESemaine.eMardi nous avons toujours 3 pour résultat, mais maintenant, ESemaine.eMercredi prend la valeur 4.
Écritures
Comme dans l'exemple précédent, on peut écrire directement les valeurs en "dur" eLundi = 1 - eMardi = 2 - etc, c'est le plus simple.
Mais on peut aussi s'appuyer sur la constante précédente :
Enum ESemaine
[_First]
eLundi = 1
' 1
eMardi = eLundi * 2
' 2
eMercredi = eMardi * 2
' 4
eJeudi = eMercredi * 2
' 8
eVendredi = eJeudi * 2
' 16
eSamedi = eVendredi * 2
' 32
eDimanche = eSamedi * 2
' 64
[_Last]
End Enum
La valeur est un multiple de 2, donc l'écriture suivante est valide bien que peu ergonomique :
Enum ESemaine
[_First]
eLundi = 1
' 1
eMardi = 2 * 1
' 2
eMercredi = 2 * 2
' 4
eJeudi = 2 * 2 * 2
' 8
eVendredi = 2 * 2 * 2 * 2
' 16
eSamedi = 2 * 2 * 2 * 2 * 2
' 32
eDimanche = 2 * 2 * 2 * 2 * 2 * 2
' 64
[_Last]
End Enum
En d'autre terme, les valeurs sont des nombres en puissance de 2 et l'exposant du nombre est l'incrément de l'exposant précédent.
Enum ESemaine
[_First]
eLundi = 2 ^ 0
' 1 c'est inscrit dans le marbre comme pi = 3.14...
eMardi = 2 ^ 1
' 2 * 1
eMercredi = 2 ^ 2
' 2 * 2
eJeudi = 2 ^ 3
' 2 * 2 * 2
eVendredi = 2 ^ 4
' 2 * 2 * 2 * 2
eSamedi = 2 ^ 5
' 2 * 2 * 2 * 2 * 2
eDimanche = 2 ^ 6
' 2 * 2 * 2 * 2 * 2 * 2
[_Last]
End Enum
Nombre de constantes dans l'énumération
Au-delàs de 2 ^ 30 nous dépassons les capacités de stockage d'un long.
Cela revient à dire qu'une énumération construite sur ce modèle est limitée à 31 constantes.
Ce nombre peut être outrepassé, mais au prix de pirouettes périlleuses.
Nous tirerons avantage à créer une constante publique qui contient cette limite.
Nous allons créer plusieurs fonctions pour manipuler les énumérations et cette constante sera utile.
Const cs_EnumItemMax = 30
Traitement itératif d'une énumération
Avec une boucle sur notre énumération, nous retombons sur un souci déjà rencontré.
Pour boucler sur 7 constantes, il faut partir de 1 jusqu'à, 64 et avec 2 ^ 30 nous bouclons jusqu'à 1073741824 pour uniquement 30 constantes, c'est tout simplement inepte !
For i = ESemaine.eLundi To ESemaine.eDimanche
' Faire quelque chose...
Itération sur une énumération
Encore une fois, faut-il abandonner l'idée de parcourir des énumérations ?
C'est sans compter que les puissances de 2 ont plus d'un tour dans leur sac et des propriétés vraiment intéressantes.
Connaitre le nombre de constantes qui composent l'énumération permet d'avoir des boucles qui ne font que le nombre de tours nécessaires.
Nous avons besoin de deux éléments :
- La valeur de la constante la plus élevée.
C'est ici que la constante [_Last] va être d'une aide précieuse. - Le nombre maximum de données autorisées dans une énumération.
Nous utiliserons la constante créée plus haut "cs_EnumItemMax".
Public Function CountConstant(ByVal argConstant As Long, Optional ByVal argMax As Byte = cs_EnumItemMax) As Byte
Dim Puissance As Long, PuissanceMax As Long, FlagSortie As Long
CountConstant = 0
If (argConstant = 1) Then
CountConstant = 1
ElseIf (argConstant > 0) Then
' Calcul de la valeur maximale possible avec le nombre de constantes maximal autorisé.
PuissanceMax = 2 ^ argMax
' Dernière valeur de l'énumération.
FlagSortie = argConstant
If (argConstant Mod 2 <> 0) Then
FlagSortie = FlagSortie - 1
End If ' argConstant Mod 2 <> 0
For Puissance = 0 To PuissanceMax
If (2 ^ Puissance > FlagSortie) Then
Exit For
End If ' 2 ^ Puissance > FlagSortie
Next Puissance
CountConstant = Puissance
End If ' argConstant = 1
End Function
L'appel à la fonction est des plus classiques :
MsgBox CountConstant(ESemaine.[_Last])
' Affiche 7.
Il est possible de passer n'importe quelle valeur de l'énumération :
MsgBox CountConstant(ESemaine.eJeudi)
' Affiche : 4. Jusqu'à ESemaine.eJeudi, il y a 4 jours.
Ne pas oublier que VB ne fait pas de contrôle de type avec les énumérations.
L'argument peut être de type "Long" et non pas spécialisé vers une énumération. Ainsi la même fonction marche quel que soit l'énumération.
Function CountConstant(ByVal argLast As Long) As Byte
MsgBox CountConstant(65)
' Ou MsgBox CountConstant(ESemaine.[_Last])
Maintenant que nous connaissons le nombre de constantes, il devient facile de réaliser une boucle :
For i = ESemaine.[_First] To CountConstant(ESemaine.[_Last]) -1
MsgBox i
Exposant d'une constante
Connaissant le nombre de données membres, il est simple de retrouver l'exposant d'une valeur puisque c'est le nombre de constantes trouvées moins 1.
Function Exposant(byVal argEnum As long) As Byte
Exposant = CountConstant(argEnum) - 1
End Function
MsgBox Exposant(ESemaine.eJeudi)
' Affiche 3 => ESemaine.eJeudi = 2 * 2 * 2 = 2 ^ 3.
Autre façon de le calculer
Function ExposantBis(ByVal argEnum As Long) As Byte
Dim Nombre As Long
Nombre = argEnum
ExposantBis = 0
Do While Nombre >= 2
Nombre = Nombre / 2
ExposantBis = ExposantBis + 1
Loop
End Function
MsgBox Exposant(ESemaine.eJeudi)
' Retourne 3 => 2 * 2 * 2 = 2 ^ 3 = 8.
Valeur de l'exposant de la constante
En connaissant l'exposant, nous pouvons retrouver la valeur associée.
Calculer la valeur correspondante à un exposant n'est pas sorcier, c’est 2 puissance exposant :
Function ValueByExposant(ByVal argExposant As Byte) As Long
ValueByExposant = 2 ^ argExposant
End Function
MsgBox ValueByExposant(3)
' Retourne 8 soit eSemaine.eJeudi.
Associer des données
Trouver le texte associé à une constante
Avec un tableau de chaine, le texte à retrouver est le texte dont l'indice est l'exposant.
Dim MesJours As Variant
MesJours = Array("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
MsgBox MesJours(Exposant(ESemaine.eJeudi))
Trouver la constante associé à un texte
Function FindEnumFromText(ByRef argArray As Variant, ByVal argString As String) As Long
Dim i As Long, Exposant As Byte
FindEnumFromText = 0
For i = LBound(argArray) To UBound(argArray)
' Parcourir les chaines
If (argArray(i) = argString) Then
' On a trouvé.
Exposant = i
' L'exposant de notre constante est l'indice du tableau.
Exit For
End If ' argArray(i) = argString
Next i
FindEnumFromText = ValueByExposant(Exposant)
' Avec l'exposant on retrouve la valeur de la constante.
End Function
MsgBox FindEnumFromText(MesJours, "Jeudi")
' Retourne 8 (ESemaine.eJeudi).
J'utilise ici un simple tableau de chaines, mais rien n'empêche de manipuler un tableau de type (structures).
Notre énumération devient un réel système de pilotage des données.
Naviguer dans l'énumération
Atteindre la première valeur
C'est ESemaine.[_First] + 1 ou plus simplement 1..
Atteindre la dernière constante
De la même manière nous utilisons ESemaine.[_Last] - 1..
Constante précédente
C'est la donnée membre divisée par 2.
Cette technique simpliste s'appelle un décalage de bits à droite..
Elle a déjà été utilisée pour retrouver l'exposant dans la fonction "ExposantBis(...)".
Function PositionPrevious(ByVal argEnum As Long) As Byte
PositionPrevious = argEnum / 2
' Décalage droite.
End Function
MsgBox PositionPrevious(ESemaine.eJeudi)
' Retourne 4 (ESemaine.eMercredi).
Constante suivante
À contrario, pour retrouver la valeur suivante, on fait un décalage de bits à gauche en multipliant la valeur par 2.
Function PositionNext(ByVal argEnum As Long) As Long
PositionNext = argEnum * 2
' Décalage gauche.
End Function
MsgBox PositionNext(ESemaine.eJeudi)
' Retourne 16 (ESemaine.eVendredi).
Additionner des constantes
Nous avons vu qu'avec ce système, nous pouvions additionner des valeurs sans risque de tomber sur des doublons.
Dim WE As ESemaine
WE = ESemaine.eSamedi + ESemaine.eDimanche
' 96 (32 + 64)
C'est ce que fait MsgBox quand on écrit vbCritical + vbOKOnly + vbMsgBoxHelpButton et l'A.P.I. Windows fait un usage intensif de ce concept.
Opérateur Or
Bien que le signe plus soit admis pour combiner les valeurs, préférez l'opérateur "Or".
Dim WE As ESemaine
WE = Esemaine.eSamedi Or ESemaine.eDimanche
' Retourne 96 (32 + 64).
Il serait d'alleurs plus juste d'utiliser MsgBox de cette manière.
vbCritical Or vbOKOnly Or vbMsgBoxHelpButton
' vbCritical = 16, vbOKOnly = 0, vbMsgBoxHelpButton = 16384 (2 ^ 14).
Toujours utiliser l'opérateur "Or" pour ajouter des données à l'énumération.
L'opérateur plus (+) fonctionne bien lors de l'initialisation, mais lors de l'ajout à une énumération existante le résultat risque de ne pas être celui escompté.
Dim WE As ESemaine
WE = ESemaine.eSamedi + ESemaine.eDimanche
' Ok, initialisation.
WE = WE + ESemaine.eVendredi
' Pas sûr.
Si je veux rajouter un vendredi de RTT, l'usage de l'opérateur Or est requis :
WE = WE Or ESemaine.eVendredi)
' Ok, sûr ! Retourne 112 (16 + 96).
Valeur totale de l'énumération
Le total des valeurs (soit tous les jours de la semaine) c'est l'ensemble des valeurs :
Dim ValueTotal As Long
ValueTotal = ESemaine.eLundi Or ESemaine.eMardi Or ESemaine.eMercredi Or ESemaine.eJeudi Or ESemaine.eVendredi Or ESemaine.eSamedi Or ESemaine.eDimanche.
Ce qui retournera la valeur 127 conforme aux propriétés du binaire comme je l'explique sur mon blog Pompe au Net.
Cette valeur peut être calculée autrement. C'est la dernière valeur ESemaine.eDimanche multipliée par 2 à laquelle nous enlèvons 1 :
ValueTotal = (ESemaine.eDimanche * 2) - 1
.
ValueTotal = ((ESemaine.[_Last] - 1) * 2) - 1
.
Tester l'énumération
Tester une seule constante
Rappelons-nous qu'une constante est de type long. On peut donc lui passer n'importe quelle valeur même si elle n'existe pas dans l'énumération.
Dim MonJour As ESemaine
MonJour = 23564
' VB accepte sans broncher !.
Nos deux membres cachés vont limiter l'impact de ce qu'il faut bien appeler un non-sens.
If (MonJour <= ESemaine.[_First]) Then
' Erreur => Trop petit !.
If (MonJour >= ESemaine.[_Last]) Then
' Erreur => Trop grand !.
Tester une combinaison à l'aide de l'opérateur And
Je souhaite savoir si le vendredi est compté comme week-end, l'opérateur "And" permet de savoir si une valeur est dans une énumération
Cet opérateur retourne vrai si la valeur fait partie de l'énumération et faux en cas inverse.
Dim WE As ESemaine
WE = Esemaine.eSamedi Or ESemaine.eDimanche
' Retourne 96 (32 + 64)..
MsgBox WE And ESemaine.eVendredi
' False, eVendredi n'est pas dans le nombre..
MsgBox WE And ESemaine.eSamedi
' True, eSamedi est bien dans la variable WE..
Quand les deux existent, c'est leur somme qui est retournée :
MsgBox WE And (ESemaine.eSamedi Or ESemaine.eDimanche)
' Retourne 96 (32 + 64)..
Quand un seul existe, l'expression renvoie sa valeur :
MsgBox WE And (ESemaine.eVendredi Or ESemaine.eSamedi)
' Retourne ESemaine.eSamedi (32)..
Quand aucun n'existe, la valeur 0 est renvoyée :
MsgBox WE And (ESemaine.eJeudi Or ESemaine.eVendredi)
' Retourne 0..
Supprimer une valeur de l'énumération
Nous avons la constante suivante WE = ESemaine.eVendredi Or ESemaine.eSamedi Or ESemaine.eDimanche à laquelle je souhaite retiré le vendredi.
Il suffit de combiner les opérateurs "And" et "Not" :
WE = WE And Not ESemaine.eVendredi
' We n'est plus composé que de eSamedi et eDimanche.
Switch de valeurs (Xor)
L'opérateur XOr est un peu particulier, en ce sens que son comportement change selon la composition de l'énumération.
XOr ajoute une valeur si elle n'existe pas dans la constante, ou l'enlève si elle existe déjà.
C'est utile pour basculer d'un état à l'autre comme on peut le faire avec l'opérateur Not :
btnA.Enabled = Not btnA.Enabled.
Reprenons notre déclaration sur les weeek-end :
Dim WE As ESemaine
WE = ESemaine.eSamedi Or ESemaine.eDimanche
WE = WE Xor ESemaine.eVendredi
Comme eVendredi n'existe pas dans l'énumération WE, il est rajouté. La constante devient ESemaine.eVendredi Or ESemaine.eSamedi Or ESemaine.eDimanche.
WE = WE Xor ESemaine.eVendredi
Comme ESemaine.eVendredi existe dans l'énumération ESemaine, il est supprimé. La constante devient Esemaine.eSamedi Or ESemaine.eDimanche.
Décomposer un nombre
Avoir la valeur 96 dans la variable WE n'apporte pas grand-chose si l'on ne sait pas retrouver les valeurs qui la composent à savoir ESemaine.eSamedi Or ESemaine.eDimanche.
Une solution est d'utiliser un tableau de booléens qui contient "True" si l'élément est sélectionné, "False" en cas inverse.
Nous créons une fonction qui attend :
- la valeur maximale de l'énumération (typiquement le membre masqué [_Last])
- la valeur du nombre composé (par exemple eJeudi Or eSamedi).
La fonction retournera le tableau de Booléens.
Function Decode(ByVal argLast As Long, ByVal argValue As Long) As Boolean()
Dim m_Count As Byte, Puissance As Long, i As Long
Dim ExistConst() As Boolean ' Le tableau qui sera retourné.
Puissance = argValue
' La valeur à décomposer.
m_Count = CountConstant(argLast)
' Nombre de constantes pour dimensionner le stockage.
ReDim ExistConst(m_Count)
' On stocke ici.
For i = 1 To m_Count
' Pour toutes les constantes.
ExistConst(i - 1) = -1 * (Puissance Mod 2 ^ i) \ 2 ^ (i - 1)
' On calcule.
Next
Decode = ExistConst
End Function
L'appel de la fonction
Dim mySelection() As Boolean ' Tableau des sélections.
' On passe la valeur max. de l'énumération et les valeurs sélectionnées.
mySelection = Decode(ESemaine.[_Last], ESemaine.eLundi Or ESemaine.eVendredi)
' Il ne reste qu'à lire notre tableau.
Dim i As Long
For i = LBound(mySelection) To UBound(mySelection) - 1
MsgBox mySelection(i)
Next i
Le tableau retournera les booléens suivants :
Indices | Valeurs | Jours |
---|---|---|
0 | True | Lundi |
1 | False | Mardi |
2 | False | Mercredi |
3 | False | Jeudi |
4 | True | Vendredi |
5 | False | Samedi |
6 | False | Dimanche |
Avec l'indice du tableau qui représente l'exposant de la valeur. Il devient simple de retrouver la valeur correspondante à l'aide de la fonction "ValueByExposant(...)" écrite plus haut.
Composer un nombre
Nous pouvons modifier le tableau reçu à l'étape précédente.
mySelection(2) = True
' Sélectionne le mercredi, j'ai maintenant une nouvelle valeur dans mon nombre.
Dans ce cas, le nombre n'est plus valide et il faut le reconstruire.
La fonction suivante attend un tableau de booléens en paramètres et retourne la valeur des constantes sélectionnées (Long).
Function Encode(ByRef argBoolean() As Boolean) As Long
Dim i As Long, myNumber As Long
myNumber = 0
For i = LBound(argBoolean) To UBound(argBoolean)
If (argBoolean(i)) Then
myNumber = myNumber + ValueByExposant(i)
' Convertir l'exposant en nombre.
End If
Next i
Encode = myNumber
End Function
Appel de la fonction
Dim MonJour As Long
MonJour = Encode(mySelection)
' Passage du tableau de booléens.
MsgBox MonJour
La variable MonJour prend la valeur 21.
Dans le tableau Lundi et vendredi sont déjà cochés ce qui donne 17. Après l’ajout du mercredi de valeur 4, nous obtenons bien 21.
Un exemple concret
Gérer des choix multiples
Nous allons, gérer des cases à cocher (checkbox) sur un formulaire à l'aide de notre énumération.
Il faut pouvoir sélectionner des jours qui doivent être convertis en nombre et si nous passons un nombre que les bonnes cases soient cochées.
Nommer les cases à cocher
La façon de nommer les checkbox a son importance et elles répondent toutes au même schéma, à savoir :
- Un libellé
Il sera commun à toutes les cases à cocher par exemple "chkJour". - Un indice
Il est concaténé à la suite du libellé.
La première checkbox porte l'indice 0.
J'ai ainsi les noms "chkJour0" qui représente ESemaine.eLundi, "chkJour1", "chkJour2",..., jusqu'à "chkJour6" pour ESemaine.eDimanche.
Si vous fait attention, cet indice n'est ni plus ni moins que la puissance de nos constantes.
Attribuer les "Captions" aux contrôles lors de l'initialisation
Il est possible de manipuler un contrôle par son nom avec l'instruction :
Me.Controls("Nom_Control").Caption = "..."
Reprenons notre tableau de chaines :
MesJours = Array("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
L'indice de l'array est aussi l'indice de la checkbox. Il suffit donc de reconstituer le nom du contrôle avec l'indice du tableau.
Cette fonction doit être dans l'UserForm. Le mieux étant de l'appeler sur le Initialize().
Private Sub UserForm_Initialize()
' Définir les "Captions" des cases à cocher.
Dim i As Long
For i = LBound(MesJours) To UBound(MesJours)
Me.Controls("chkJour" & i).Caption = MesJours(i)
' Affectation du "Caption".
Next
End Sub
Cocher les bonnes cases
Il faut ensuite cocher les cases dont le jour est dans notre énumération.
C'est la même logique que la procédure précédente sauf que nous affectons la propriété "Value".
Nous écrivons dans le formulaire une fonction qui appellera la fonction "Decode(...)" vue précédemment.
Nous récupérons donc un tableau de booléens, il est alors facile d'attribuer aux coches la bonne valeur.
Private Sub DecodeDay()
Dim i As Long
' Tranformer la valeur de l'attribut en booléen.
' Pour savoir s'il faut cocher ou non selon que l'attribut existe ou pas dans la portée.
' Contient l'état des constantes composant le nombre.
Dim myCoche() As Boolean
myCoche = Decode(ESemaine.[_Last], m_DateSample)
' m_DateSample = le nombre à décoder.
For i = LBound(myCoche) To UBound(myCoche) - 1
' Reconstruire le nom de la coche à affecter.
' Affecter la propriété "Checked" selon que l'élément est dans notre nombre ou pas.
Me.Controls("chkJour" & i).Value = myCoche(i)
Next
End Sub
Exempe d'appel sur UserForm_Initialize()
Private m_DateSample As ESemaine
Private Sub UserForm_Initialize()
m_DateSample = ESemaine.eLundi Or ESemaine.eDimanche Or ESemaine.eMercredi Or ESemaine.eMardi
' Procédure à appeler chaque fois que la valeur de l'énumération m_DateSample change.
DecodeDay
Convertir les coches en valeurs de l'énumération
Si les cases "Lundi" et "Mardi" sont cochées, il faut attribuer la valeur 3 au nombre sous-jacent aux coches, c'est donc l'addition des indices des cases qui sont cochées.
Un tableau de booléens de taille égale aux nombres de constantes de l'énumération est remplit avec les valeurs des coches. Il est ensuite transmis à la fonction "Encode()" écrite plus haut.
Voici la fonction dans l'UserForm.
Private Sub EncodeDay()
Dim m_Count As Byte
Dim m_Day() As Boolean, i As Long
Dim m_Jour As ESemaine
m_Count = CountConstant(ESemaine.[_Last])
ReDim m_Day(m_Count)
' Prêt à recevoir les booléens.
For i = LBound(m_Day) To UBound(m_Day) - 1
' Parcourir les éléments de l'énumération.
m_Day(i) = Me.Controls("chkJour" & i).Value
' Donner la valeur de la coche.
Next i
m_Jour = Encode(m_Day)
' Appelle la fonction de conversion.
Me.txtDay.Text = m_Jour
End Sub
Elle sera appelée chaque fois qu'une valeur de coche change.
Private Sub chkDay0_Click()
EncodeDay
End Sub
Private Sub chkDay1_Click()
EncodeDay
End Sub
Private Sub chkDay2_Click()
EncodeDay
End Sub
Note
Rien n'empêche de générer dynamiquement les cases à cocher sur le même principe, nous avons ainsi un système automatique à choix multiple qui peut être inclus dans n'importe quel projet.
Mise en place d'un exemple d'utilisation
Dans le formulaire du classeur de démonstration, vous trouverez une ComboBox cbxDayType défini ainsi :
Private Sub UserForm_Initialize()
' Quelques valeurs exemples pour manipuler l'énumération.
cbxDayType.AddItem "Aucun jour"
cbxDayType.AddItem "Tous les jours"
cbxDayType.AddItem "Jours de travail"
cbxDayType.AddItem "Week-end"
Sur l'évènement Change() de la ComboBox, nous définissons le nombre correspondant à l'item choisi :
Private Sub cbxDayType_Change()
' Définir des valeurs pour notre énumération.
Select Case Me.cbxDayType.ListIndex
Case 0
' Aucun jour.
m_DateSample = ESemaine.[_First]
Case 1
' Tous les jours.
m_DateSample = ESemaine.eLundi Or ESemaine.eDimanche Or ESemaine.eMercredi Or ESemaine.eMardi Or ESemaine.eSamedi Or ESemaine.eJeudi Or ESemaine.eVendredi
Case 2
' Jour ouvrés.
m_DateSample = ESemaine.eLundi Or ESemaine.eMercredi Or ESemaine.eMardi Or ESemaine.eJeudi Or ESemaine.eVendredi
Case 3
' Week-end.
m_DateSample = ESemaine.eDimanche Or ESemaine.eSamedi
Case Else
MsgBox "Je n'ai pas compris votre demande !", vbInformation Or vbOKOnly
End Select ' Me.cbxDayType.ListIndex
DecodeDay
' Rafraichir les cases à cocher.
End Sub
Quand la valeur de la ComboBox change, une valeur est attribuée à l'énumération et les coches sont attribuées aux CheckBox.
Complément
Énumérations complexes
Rien n'empêche d'avoir des énumérations plus évoluées, et mélanger allègrement suite incrémentale et puissance :
Enum UneEnum
[_First]
eConstA = 1
' Énumération incrémentale.
eConstB
' 2.
eConstC
' 3.
eConstD = 0
' Indique un nouveau groupe de valeurs.
eConstE = 8
' On part sur des puissances.
eConstF = 16
eConstG = 256
' Ici on laisse un trou.
eConstH = 0
' Création d'un nouveau groupe de valeurs.
eConstI = 512
[_Last]
End Enum
Si vous allez voir dans l'aide Visual Basic la fonction MsgBox, vous retrouverez le même arrangement pour les constantes.
Les énumérations offrent de belles perspectives
Les fonctions pour les manipuler sont simples, courtes et facilement identifiables.
Comme il n'y a pas de contrôle de type de la part de VB, ce n'est pas compliqué de créer une classe de base qui reçoit et retourne des long pour gérer les énumérations.
Les usages sont nombreux puisque les énumérations gèrent des choix, ce qui est un besoin primaire de tout programme informatique.
Limites des énumérations
En extrapolant, nous pouvons dire qu'une énumération est un moyen de stocker de l'information comme peut le faire un champ dans une base de données.
Je peux imaginer un champ d'une table Microsoft Access ou d’un tableau Microsoft Excel qui conserve les valeurs combinées de l'énumération.
Exemple d'une table stockant des énumérations
CONTACT_ID | CONTACT_NOM | CONTACT_PRESENCE |
---|---|---|
1 | Toto | 20 |
2 | Titi | 1 |
3 | Tata | 45 |
Dans ce cas, j'économise la table du côté "plusieurs". En contrepartie, les données sont masquées ce qui est loin d'être conseillé.
L'accès aux données ne devrait pas dépendre d'un processus externe (code – formulaire).
Téléchargement
Télécharger le classeur exemple Énumérations en VBA (Excel 2010).
Achetez mon ouvrage !
Mon PDF « Créer un planning perpétuel sur Microsoft Excel sans macro » est disponible à la vente.
Pour plus d’informations, rendez-vous sur la page dédiée.