1. Avant-propos ▲
Ce document a pour but de vous montrer comment concevoir une liste qui voit son contenu filtré en fonction de
ce qui est inscrit dans une zone de texte.
Vous devez être relativement à l'aise avec Microsoft Access et connaître la conception de formulaires, mais
également la manipulation des requêtes avec Visual Basic en ayant une bonne approche de ce langage
afin de mettre en pratique cet exemple
1-1. Niveau ▲
Ce tutoriel s'adresse plus particulièrement aux personnes qui possèdent déjà des notions concernant la gestion des événements avec Visual Basic sur Microsoft Access incluant la manipulation des données à l'aide DAO.
1-2. Contact ▲
Pour tous renseignements complémentaires, veuillez me contacter directement (Argyronet) par MP.
Si votre question est disons publique, merci de poster un message dans le forum afin d'en faire partager le contenu...
2. Présentation du projet▲
Dans la série des listes, il y a eu le tutoriel vous permettant de
concevoir une liste qui en alimente une autre...
Au regard du succès de ce dernier, par les commentaires que j'ai pu lire d'une part et par les mails que j'ai reçus d'autre part,
je me suis laissé tenter de vous faire partager cette nouvelle aventure au sein d'une... autre liste.
Surprenant, non ?
Cette fois-ci, le petit projet permet de comprendre comment mettre en œuvre la possibilité d'obtenir les enregistrements
filtrés dynamiquement au fur et à mesure que des caractères sont saisis dans une zone de texte.
Pourquoi un tel projet ? Eh bien parce que dans une zone de liste (non déroulante), il n'y a pas possibilité de
taper des caractères pour localiser l'occurrence cherchée...
Il existe cependant une possibilité alternative qui vise à employer l'API SendMessage() accompagnée de la constante
LB_FINDSTRING mais cette méthode elle aussi nécessite un contrôle Texbox et utilise le Handle du contrôle,
chose qui reste indisponible dans les contrôles zone de liste d'Access...
Mais nous allons faire en sorte d'obtenir la même chose ici.
Dans le projet présenté, j'ai volontairement décortiqué le module et son formulaire en l'extirpant de son projet
initial pour vous en faire bénéficier.
Je précise qu'un peu de peaufinage a été mis en application dans la conception du formulaire afin que cela vous ouvre des
idées similaires au sujet même si l'objectif se limite à la compréhension du phénomène de pseudocommunication entre
deux contrôles :
- une Zone de liste déroulante et une Zone de texte via une procédure VBA traduite à travers différents
événements.
Pour mettre en œuvre ce projet, j'ai conservé la table d'origine qui contient, selon ce que j'avais pu récupérer à l'époque,
l'ensemble des villes et codes postaux correspondants.
Je n'ai plus la source en tête, mais une chose est sûre, c'est la toile
qui me l'a fournie.
Note:
Ce tutoriel peut, dans son ensemble, être mis en application pour toutes les versions d'Access.
Il est présenté ici sous Access 2010 (14)
Toutefois, certaines méthodes seront inexistantes dans les versions antérieures à la version 97.
2-1. Création de la table▲
Le projet ne comporte qu'une seule table. Celle-ci contient l'ensemble des villes et leur code postal, le tout associé à un
champ compteur (Numéro Auto). En effet, des mêmes codes postaux peuvent être affiliés à plusieurs villes et inversement.
La table est composée de trois champs :
- le 1er champ définit l'identifiant de l'association Code postal + Ville et sera la clé primaire ;
pour plus de commodité, nous appellerons ce champ IDPostalCode.
Le préfixe ID définit le fait que ce champ est l'identifiant de l'enregistrement ;
- Le 2e champ PostalCode définit le numéro du code postal sur 5 caractères (France) ;
- Le 3e champ Town définit le nom de la commune sur 100 caractères (suffisant pour le nom).
Pour les champs PostalCode et Town, affectez-leur un index avec doublon.... Ce point est primordial
N'attribuez pas d'autres propriétés à la table que celles affectées par défaut pour ces champs.
En effet, le code régi dans le formulaire n'impacte pas la table directement, en tout cas pour ce qui concerne le sujet
de ce tutoriel.
Par principe, ne mettez jamais d'accents ni d'espaces dans le nom des champs. C'est pourquoi je nomme toujours mes champs en anglais.
Pour plus d'information sur la convention de nommage des objets, vous pouvez lire
ce tutoriel.
Vous enregistrez la table sous le nom TBLPostalCodes.
2-1-1. Vue de la table en mode création▲
La table en mode création une fois les champs créés et la table enregistrée.
2-2. Remplissage des tables▲
Pour pouvoir tester le bon fonctionnement de ce projet, vous devez entrer un jeu de codes postaux et les communes
qui leur correspondent. Vous pourrez en récupérer la liste à la fin de ce tutoriel.
Le champ de clé primaire s'incrémente de lui-même par sa propriété Numéro Auto.
Pour la petite histoire, j'ai eu à poser ce type de champ pour la communication entre tables, parce qu'il
il était plus commode de chercher une association Code postal + Ville par cet identifiant que par l'association
sous forme de chaîne de caractères telle que l'imposent les contenus respectifs des deux champs de données.
En effet, un code postal autant qu'une ville peut être affecté à plusieurs correspondances identiques ce qui
contraint d'établir une condition de recherche (WHERE) avec les deux occurrences :
Town =
"????"
AND
PostalCode =
"?????"
2-2-1. Vue de la table en mode feuille de données▲
La table en mode feuille de données une fois les enregistrements saisis ou collés.
Note : Le numéro de la clé primaire n'a rien de commun avec les codes postaux...
3. Création du formulaire▲
Vous créez donc un nouveau formulaire vide (Mode création) où vous y posez quelques contrôles entre autres zones de liste modifiable (ComboBox), étiquettes (Label) et boutons de commande (CommandButton)...
De haut en bas :
1/ vous dessinez un jeu de deux étiquettes superposées décalées d'un pixel que vous nommez lblTitle1 et lblTitle2 et
contenant le titre du formulaire en Times New Roman 20 Gras italique avec une couleur claire pour l'étiquette de premier plan et une
couleur foncée pour celle de l'arrière-plan.
Le mieux pour créer ce type de titre est d'en dessiner un seul, de lui affecter les propriétés voulues et enfin de le dupliquer. Une fois dupliqué, vous le superposez après l'avoir nommé et attribué sa couleur.
2°) vous dessinez une autre étiquette qui stipule succinctement le mode opératoire et que vous nommez lblInfo ;
3°) vous dessinez au-dessous une zone de texte et que vous nommez txtLookup ;
4°) juxtaposée à cette zone de texte, vous dessinez un bouton (Berci Saint-Exupéry) que vous nommez cmdEraseEntry.
Vous lui affectez une image idoine. Celle qui est illustrée a été piochée dans la liste des images au niveau de la propriété de même nom pour
ce contrôle ;
5°) de façon facultative, vous dessinez les deux séparateurs rouges : l'un horizontal et l'autre vertical ;
6°) vous dessinez au-dessous dans des proportions adaptées la zone de liste que vous nommez lboPCTowns ;
C'est ce contrôle qui va recevoir la liste des villes et codes postaux associés. Vous définissez sa taille en cm
selon la police que vous lui affecterez ; ainsi, pour du Tahoma 12, une taille de 9,2 cm x 5 cm est bien proportionnée.
Vous pouvez la « relooker » en lui attribuant un effet 3D...
D'un point de vue ergonomique, si vous attribuez un effet 3D enfoncé à un contrôle, arrangez-vous pour que les autres qui sont prédominants subissent le même sort.
7°) vous dessinez au-dessous et de la même longueur, une étiquette que vous nommez lblCountTowns.
Celle-ci est chargée d'afficher le nombre d'occurrences trouvées qu'il y ait recherche filtrée ou non.
À l'intérieur de ce contrôle qui se voit attribuer un effet 3D enfoncé, vous dessinez deux miniboutons qui
serviront respectivement à modifier une ville et son code postal ou à ajouter ces deux éléments dans la base...
En effet, il n'est pas obligatoire d'avoir toutes les villes listées dans la table. Au départ, ce projet ne ciblait que
les personnes habitant dans le 92 donc l'utilisateur m'avait demandé de ne mettre que les communes qui jouxtent celle
de son lieu d'utilisation de l'application et c'est pourquoi j'avais pris l'initiative d'ajouter ces contrôles.
Dans la mesure où vous incorporez toutes les villes de France, il est inutile de dessiner ces boutons.
8°) vous dessinez plus bas un rectangle en 3D relevé avec une faible hauteur que vous nommez shpSeparator ;
Il sert juste de séparateur entre le haut et le bas. Euh..., c'est juste pour faire joli, mais d'après moi, bien entendu.
9°) vous dessinez alors au-dessous de ce trait un autre rectangle en 3D enfoncé que vous nommez shpButtons ;
C'est dans ce rectangle que nous allons dessiner trois boutons de commande...
10°) vous dessinez les trois boutons que vous nommez et intitulez de gauche à droite :
- cmdClose : &Fermer
- cmdInvert : Ville / C&ode postal
- cmdValid : &Valider
Notez le symbole & jouxtant un caractère de la légende (Caption).
Ce caractère est utilisé pour définir la touche d'accès rapide lorsque celle-ci est combinée à la touche ALT.
Ces trois boutons ont pour rôle respectif :
- cmdClose : fermer la fenêtre sans autre action - code qui équivaut à l'action Annuler
- cmdInvert : inverser l'ordre des colonnes de la liste Ville / Code postal et Code postal / Ville
- cmdValid : valider la sélection : initialement, ce formulaire était voué à appliquer au formulaire
appelant la valeur sélectionnée dans la liste pour renseigner la ville et le code postal du candidat.
Rappelez-vous la clé primaire... IDPostalCode
Il est évident que transporter les informations souhaitées d'un enregistrement via sa clé d'identité est somme toute plus simple.
S'il n'y en avait pas eu, il aurait fallu alors détecter l'ordre des colonnes choisies
d'une part et transporter les deux éléments d'autre part dans des variables distinctes ou sous forme de tableau pour
que leur valeur puisse s'insérer dans les zones voulues.
Embellissement du formulaire
Vous pouvez remarquer le dégradé en arrière-plan de formulaire exemple. Il s'agit simplement d'une image
appliquée en mosaïque.
Pour le reste, arrangez un peu la disposition des contrôles pour que tout soit aligné et proportionnel.
C'en est fini de la conception du formulaire ; maintenant, nous allons attaquer la partie propriétés pour les contrôles.
Vérifiez une dernière fois que vous avez bien nommé les contrôles. Cela est important... Euh... Oui, je sais, je l'écris
à chaque fois dans mes tutoriels, mais c'est juste pour que ça devienne un réflexe, surtout pour les nouveaux.
3-1. Affectation des propriétés aux contrôles▲
Seule la zone de liste reçoit des propriétés initiales... Bien que celles-ci pourraient être définies par code, je vous montre
ici l'alternative de manière à ce que vous distinguiez bien la différence entre un contrôle nanti de ses propriétés définies par
l'utilisateur et celles définies dynamiquement par le code VBA en fonction d'une situation donnée.
Dans le contexte, la liste est censée afficher des codes postaux et des villes...
Comme il semble qu'il soit plus aisé de retenir un nom de commune plutôt qu'un code postal, dans le sens abstrait du terme,
j'ai délibérément choisi d'afficher par défaut et donc au chargement du formulaire, la liste avec les communes à gauche
et le code postal en face, à droite, donc.
Cela se traduit par une requête que vous allez créer et nommer qry_LBOListTownsCP avec les paramètres suivants :
ID | Ville | Code postal avec ordre de tri sur la ville
SELECT
IDPostalCode, Town, PostalCode
FROM
TBLPostalCodes
ORDER
BY
Town;
En parallèle à cela et dans le même élan, puisque vous y êtes, créez également la requête inverse, cette fois-ci nommée
qry_LBOListCPTowns, avec les paramètres suivants :
ID | Code postal | Ville avec ordre de tri sur le code postal
SELECT
IDPostalCode, PostalCode, Town
FROM
TBLPostalCodes
ORDER
BY
PostalCode;
Ainsi, et selon vos préférences, vous pourrez indifféremment affecter la requête souhaitée à votre liste sans oublier
de changer la légende du bouton cmdInvert selon le cas.
En effet, vous allez affecter à la zone de liste la requête qui liste les communes et codes postaux (dans cet ordre).
3-1-1. Propriétés de la liste des communes▲
Vous attribuerez aux propriétés :
- Origine source : Table/Requête
- Contenu : qry_LBOListTownsCP
- Nbre de colonnes : 3
- Largeurs colonnes : 0cm;7cm;1,6cm;
- Colonne liée : 3
Explications :
- 0cm pour la 1re colonne qui est la clé primaire et que nous ne voulons pas voir apparaître
- 1,6cm pour la 2e colonne réservé au code postal est suffisant pour les cinq caractères ;
apparaître dans la liste déroulante ;
- 7cm pour la 3e colonne réservés au nom de la commune (certaines sont vraiment
longues en matière de nombre de caractères).
Remarque : l'ordre 1,6 cm et 7 cm est inversé lors de l'inversion provoquée par le clic du bouton...
Lorsque vous prenez l'Assistant Création Contrôle et que vous lui demandez de
vous aider dans la conception d'une zone de liste déroulante
sur telle table ou telle
requête, il vous propose par défaut de masquer la colonne numéro 1 (si elle est définie comme primaire)
puisqu'il est sous-entendu (normalisation oblige),
que vous avez effectivement mis un
identifiant UNIQUE pour représenter chacun des enregistrements de la table source.
Bien entendu, cela est le cas si votre table possède au moins deux champs tels que cela est conçu
dans la table de ce projet.
Il en est de même si vous construisez le contrôle manuellement…
Il vous appartiendra alors d'ajuster la taille des colonnes en conséquence...
3-1-2. Les propriétés des autres contrôles▲
Toutes les propriétés des autres contrôles restent celles par défaut à l'exception des boutons
cmdUpdate et cmdValid qui ont leur propriété Activé (Enabled) définie à Non (False).
Leur état sera modifié dynamiquement dans le code en fonction de ce qui se passe dans le formulaire.
3-1-3. Disposition des contrôles▲
Nous avons vu en début de paragraphe un aperçu d'une disposition qui reste avant tout une suggestion.
Ayant quelques (bonnes) années de pratique sur le sujet, je me suis permis de vous proposer ce look mais rien
ne vous empêche d'agencer ce formulaire comme bon vous semble.
Ordre de tabulation
Dans ce formulaire, vous devez affecter à la propriété Arrêt tabulation (TabStop) la valeur Oui
pour les contrôles de zone de texte (Textbox), zone de liste (Listbox) et pour les trois boutons
du bas (CommandButton).
L'ordre sera alors 0 pour la zone de texte, 1 pour la zone de liste et de 2 à 4 pour les trois
boutons en partant de la droite vers la gauche...
Il n'est en effet pas ou peu utile de donner les focus par la touche TAB aux autres contrôles du fait
qu'il n'y a pas lieu d'intervenir dessus en utilisation disons standard.
Exécution du formulaire en l'état
Vous pouvez passer en mode formulaire (ou appuyer sur F5) pour voir si tout concorde et si la liste
est bien alimentée par l'ensemble des villes.
Cela doit ressembler à :
Vu qu'aucun code n'a été affecté au formulaire et ses contrôles, il est évident que rien ne se passe lorsque
vous écrivez dans la zone de texte...
3-1-4. Mise en place du code événement▲
Pour affecter des événements aux différents contrôles, différentes solutions sont possibles...
Tout d'abord...
Pour mettre en place le code événement, il se peut que vos options ne soient pas définies pour attaquer à chaque fois
l'éditeur Visual Basic (VBE)...
Aussi, pour rendre plus confortable la rédaction du code, je vous invite à forcer l'usage de procédures
événementielles pour chaque événement et éviter l'apparition systématique de cette boîte de dialogue :
Veuillez activer les options d'Access et cocher la case appropriée dans la rubrique concernée :
la rubrique est intitulée :
"Toujours utiliser les procédures événementielles"...
Passer dans l'éditeur Visual Basic
À l'extérieur du formulaire tout en restant dans la fenêtre de celui-ci, effectuez un clic droit et choisissez
la commande : "Créer code événement…"
Dans l'éditeur Visual Basic, une fenêtre blanche va vous proposer d'écrire
un bout de code correspondant à l'événement attaché au chargement du formulaire puisque le fait d'effectuer un clic
droit sur la fenêtre correspond à ce dernier.
Option
Explicit
Option
Compare Database
Private
Sub
Form_Load
(
)
End
Sub
Cet événement s'appelle Form_Load qui de par sa traduction signifie ce qu'il fait...
Pour plus d'informations sur les événements des contrôles et autres objets, veuillez vous référer à l'aide en ligne en mettant en surbrillance l'événement souhaité et appuyez sur la touche F1.
Rappelez-vous qu'un événement est quelque chose qui se passe lorsque l'utilisateur effectue une certaine opération "avant", "pendant", "après", etc. sur tel ou tel contrôle.
Il faut que vous sachiez qu'un contrôle de type zone de liste ne possède pas les
mêmes événements d'un contrôle bouton de commande par exemple et pour autant,
ils ont des événements communs.
Il vous appartiendra alors de choisir l'événement le plus approprié lorsque tel ou
tel contrôle est utilisé dans votre formulaire.
3-1-4-1. Description des événements du projet▲
Pour ne rien vous cacher, la page de code Visual Basic pour ce formulaire contient grosso modo 300 lignes
de code. Mais je n'ai volontairement pas laissé toutes les fonctions telles qu'elles existaient, car certaines
d'entre elles ne pouvaient pas s'appliquer à vos développements existants.
Bref, le code est assez simple en soi à comprendre, mais il faut tout de même avoir déjà abordé ce langage car
en plus d'être dommage, il est plutôt frustrant de copier-coller du code sans comprendre à quoi il sert et ce
qu'il fait.
Liste des procédures et fonctions de la classe de formulaire
- cmdAdd_Click() Appelle AddNewTown pour ajouter une ville
- cmdClose_Click() Ferme le formulaire
- cmdEraseEntry_Click() Permet d'effacer la zone de texte AddNewTown
- cmdInvert_Click() Permet d'inverser la sélection Ville/Code postal - Code postal/Ville
- cmdUpdate_Click() Permet de modifier une ville ou un code postal
- cmdValid_Click() Permet de valider la commune sélectionnée pour la déposer dans un formulaire appelant
- CountTownFound() Fonction qui renseigne le nombre de communes trouvées (selon le critère)
- Form_Close() Événement Close (Sur fermeture) du formulaire
- Form_KeyPress() Événement KeyPress (Sur touche activée) du formulaire
- Form_Load() Événement Load (Sur chargement) du formulaire
- lboPCTowns_Click() Événement Click (Clic) de la zone de liste
- txtLookup_KeyPress() Événement KeyPress (Sur touche activée) de la zone de texte
- AddNewTown() : Procédure permettant d'ajouter une ville dans la base
- SetListOfCityProperties() Procédure qui définit les propriétés de la zone de liste
- ThisTownExists() Fonction qui vérifie l'existence d'une commune
- RemoveCharAccents() Fonction qui substitue les accents d'un chaîne de caractères
- UpdateTownList() Procédure qui rafraîchit la liste des communes en fonction d'un critère
La liste ci-dessus représente l'ensemble des procédures (événementielles ou non) qui sont appliquées au formulaire...
3-1-5. Rédaction du code du formulaire▲
Voici l'ensemble des blocs de code de la classe du formulaire :
'**********************************************************************
' Module : Form_frmTowns
' Type : Document VBA
' DateTime : 20/09/2011
' Author : Jean-Philippe AMBROSINO
' Review : Jean-Philippe AMBROSINO
' Purpose : Module de sélection d'une ville selon un critère
'
''**********************************************************************
Option
Explicit
Option
Compare Database
'Variable objet d'instance de la base de données
Private
m_oDB As
DAO.Database
'Variable récupérant la sélection de la liste
Private
m_strPostalCode As
String
Private
m_strTown As
String
Private
m_lngIDPostalCode As
Long
'Variable de la frappe en cours dans la zone de texte
Private
m_strCurrentChars As
String
'Constantes des requêtes
Private
Const
TOWNCP_QUERY As
String
=
"qry_LBOListTownsCP"
Private
Const
CPTOWN_QUERY As
String
=
"qry_LBOListCPTowns"
'Constantes de légende du bouton d'inversion
Private
Const
CPTOWN_CAPTION As
String
=
"Code Postal / Ville"
Private
Const
TOWNCP_CAPTION As
String
=
"Ville / Code Postal"
'Constantes de largeurs de colonnes
Private
Const
CPT_TOWN_COLWIDTH As
String
=
"0cm;1,6cm;7cm"
Private
Const
TOWN_CPT_COLWIDTH As
String
=
"0cm;7cm;1,6cm"
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
cmdAdd_Click
(
)
'Appelle la fonction AddNewTown pour ajouter une ville dans la base
If
_
MsgBox
(
"Normalement, vous n'avez pas à créer de nouvelles villes car elles ont toutes été implémentées dans la base..."
_
&
vbCrLf
&
"Toutefois, il est probable qu'un oubli ait été commis."
&
_
vbCrLf
&
vbCrLf
&
"Voulez-vous ajouter une autre ville ?"
, vbExclamation
+
vbYesNo
+
vbDefaultButton2
, _
"Ajouter une ville"
) =
vbYes
Then
AddNewTown
End
If
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
cmdClose_Click
(
)
'Ferme le formulaire sans autre action (on peut y greffer un message de confirmation)
DoCmd.Close
acForm, Me.Name
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
cmdEraseEntry_Click
(
)
'Efface la zone de texte, remet les contrôles à l'état initial et réinitialise la variable
m_strCurrentChars =
vbNullString
cmdInvert.Caption
=
TOWNCP_CAPTION
SetListOfCityProperties TOWNCP_QUERY, TOWN_CPT_COLWIDTH
With
Me.txtLookup
.Value
=
vbNullString
.SetFocus
End
With
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
cmdInvert_Click
(
)
'Inverse la sélection et adapte la source de la liste en conséquence
m_strCurrentChars =
vbNullString
Select
Case
cmdInvert.Caption
Case
CPTOWN_CAPTION
SetListOfCityProperties TOWNCP_QUERY, TOWN_CPT_COLWIDTH
Me.cmdInvert.Caption
=
TOWNCP_CAPTION
Case
TOWNCP_CAPTION
SetListOfCityProperties CPTOWN_QUERY, CPT_TOWN_COLWIDTH
Me.cmdInvert.Caption
=
CPTOWN_CAPTION
End
Select
'Désactive les boutons
Me.cmdUpdate.Enabled
=
False
Me.cmdValid.Enabled
=
False
With
Me.txtLookup
.Value
=
vbNullString
.SetFocus
End
With
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
cmdUpdate_Click
(
)
'Permet de mettre à jour une ville ou un code postal : le formulaire est à construire
Dim
strCurrentSelection As
String
strCurrentSelection =
IIf
(
cmdInvert.Caption
=
CPTOWN_CAPTION, _
Me!lboPCTowns.Column
(
1
) &
" "
&
Me!lboPCTowns.Column
(
2
), _
Me!lboPCTowns.Column
(
2
) &
" "
&
Me!lboPCTowns.Column
(
1
))
If
MsgBox
(
"Souhaitez-vous modifier le code postal ou la ville '"
&
_
strCurrentSelection &
"' ?"
, vbQuestion
+
vbYesNo
, "Modifier"
) =
vbYes
Then
m_lngIDPostalCode =
Nz
(
Me.lboPCTowns.Column
(
0
), 0
)
DoCmd.Close
acForm, Me.Name
'***************************************************************
MsgBox
"Formulaire cible à définir..."
, vbInformation
, _
"Mise en commentaire"
'DoCmd.OpenForm "Formulaire_De_Mise_A_Jour", , , "IDPostalCode=" & m_lngIDPostalCode, _
acFormEdit, acDialog, strCurrentSelection
'***************************************************************
End
If
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
cmdValid_Click
(
)
'Récupère la sélection effectuée sur la liste et la dépose dans les zones appropriées du formulaire appelant
'On entend par formulaire appelant, le formulaire qui a ouvert celui-ci
Dim
m_strPostalCode As
Long
Dim
m_strTown As
String
Dim
m_lngIDPostalCode As
Long
m_lngIDPostalCode =
Nz
(
Me.lboPCTowns.Column
(
0
), 0
)
If
m_lngIDPostalCode Then
'Affectation des valeurs aux variables selon la sélection
m_strPostalCode =
IIf
(
cmdInvert.Caption
=
CPTOWN_CAPTION, Me!lboPCTowns.Column
(
1
), Me!lboPCTowns.Column
(
2
))
m_strTown =
IIf
(
cmdInvert.Caption
=
CPTOWN_CAPTION, Me!lboPCTowns.Column
(
2
), Me!lboPCTowns.Column
(
1
))
If
MsgBox
(
"Cette action va déposer la sélection :"
&
vbCrLf
&
m_strPostalCode &
" "
&
m_strTown &
" dans le formulaire..."
&
_
vbCrLf
&
vbCrLf
&
"Est-ce correct ?"
, vbQuestion
+
vbYesNo
, "Confirmation"
) =
vbYes
Then
DoCmd.Close
acForm, Me.Name
'On passe la valeur de 'ID à l'argument OpenArgs pour le récupérer...
'On aurait pu procéder à l'aide d'une variable ou d'une propriété publique
DoCmd.OpenForm
"frmAppelant"
, , , , , acDialog, m_lngIDPostalCode
End
If
End
If
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
Form_Close
(
)
'Fermer l'objet Database et distinguer la variable objet de l'objet réel qu'elle représente
If
Not
m_oDB Is
Nothing
Then
m_oDB.Close
Set
m_oDB =
Nothing
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
Form_KeyPress
(
KeyAscii As
Integer
)
'Si l'utilsateur appuie sur Echap (27) le formulaire est fermé sans confirmation
'Utilisable seulement si la propriété KeyPreview = True
If
KeyAscii =
27
Then
DoCmd.Close
acForm, Me.Name
End
If
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
Form_Load
(
)
'Active le sablier
DoCmd.Hourglass
True
'Propriété du formulaire
Me.KeyPreview
=
True
'Initialise la base de données dans la variable
Set
m_oDB =
CurrentDb
'Prépare les contrôles
Me.cmdInvert.Caption
=
TOWNCP_CAPTION
Me.cmdAdd.Enabled
=
True
Me.cmdUpdate.Enabled
=
False
Me.lblCountTowns.Caption
=
CountTownFound
(
vbNullString
)
'Initialise la liste des communes
SetListOfCityProperties TOWNCP_QUERY, TOWN_CPT_COLWIDTH
'Désactive le sablier
DoCmd.Hourglass
False
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
lboPCTowns_Click
(
)
Dim
strCurrentSelection As
String
'Affecte la sélection CP+Commune dans une variable quel que soit l'ordre des colonnes
strCurrentSelection =
IIf
(
cmdInvert.Caption
=
CPTOWN_CAPTION, _
Me!lboPCTowns.Column
(
1
) &
" "
&
Me!lboPCTowns.Column
(
2
), _
Me!lboPCTowns.Column
(
2
) &
" "
&
Me!lboPCTowns.Column
(
1
))
'Libère le bouton de validation
Me.cmdValid.Enabled
=
True
'et celui de la mise à jour
Me.cmdUpdate.Enabled
=
True
Me.labelTownSelected1.Caption
=
strCurrentSelection
Me.labelTownSelected2.Caption
=
labelTownSelected1.Caption
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
txtLookup_KeyPress
(
KeyAscii As
Integer
)
UpdateTownList KeyAscii
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
AddNewTown
(
)
'Permet d'ajouter une ville et son CP dans la base de données
Dim
oRS As
DAO.Recordset
Dim
lngNewID As
Long
Dim
strCurrentSelection As
String
'Mise en place d'une gestion d'erreurs (DAO)
On
Error
GoTo
L_ErrAddNewTown
'Demande le nom de la ville...
m_strTown =
UCase
$(
InputBox
(
"Quel est le nom de la commune à ajouter ?"
, "Nouvelle commune"
))
'Vérifie si une entrée est saisie
If
Len
(
m_strTown) =
0
Then
MsgBox
"Le nom inscrit est incorrect ou bien vous avez annulé!"
, vbExclamation
, "Erreur"
Exit
Sub
End
If
'Demande le N° du code postal...
m_strPostalCode =
InputBox
(
"Quel est le N° du code postal de cette ville '"
&
m_strTown &
"' ?"
, "Code postal de la ville"
)
'Vérifie si une entrée est saisie et comporte 5 caractères
If
Len
(
m_strPostalCode) <>
5
Then
MsgBox
"Le N° inscrit est incorrect ou bien vous avez annulé !"
, vbExclamation
, "Erreur"
Exit
Sub
End
If
'Confirmation de l'entrée (en MAJUSCULES non accentuées)...
strCurrentSelection =
m_strPostalCode &
" "
&
RemoveCharAccents
(
m_strTown, True
)
If
MsgBox
(
"Veuillez confirmer que doit être ajouté '"
&
strCurrentSelection &
"' dans la base ?"
, _
vbQuestion
+
vbYesNo
, "Ajout"
) =
vbYes
Then
'Contrôle de l'existence de la ville
If
ThisTownExists
(
m_strTown, m_strPostalCode) Then
MsgBox
"La commune '"
&
m_strTown &
_
"' existe déjà dans la base : elle a pour code postal "
&
m_strPostalCode &
"..."
, vbExclamation
, "Doublon"
Exit
Sub
Else
'Ouvre le Recordset sur la table pour ajouter les valeurs
Set
oRS =
CurrentDb.OpenRecordset
(
"TBLPostalCodes"
, dbOpenDynaset)
With
oRS
.AddNew
!PostalCode =
m_strPostalCode
!Town =
m_strTown
lngNewID =
!IDPostalCode
.update
.Close
End
With
'Met à jour la liste en conséquence
With
Me.lboPCTowns
.Requery
End
With
'Ajoute la nouvelle entrée dans la zone de texte selon l'inversion en cours
If
cmdInvert.Caption
=
CPTOWN_CAPTION Then
Me.txtLookup
=
m_strPostalCode
Else
Me.txtLookup
=
m_strTown
End
If
'Affecte l'ID à la variable comme si on l'avait sélectionnée
m_lngIDPostalCode =
lngNewID
'Et au label au-dessous de la liste
Me.labelTownSelected1.Caption
=
strCurrentSelection
Me.labelTownSelected2.Caption
=
labelTownSelected1.Caption
'Et on compte le nombre de villes trouvées
Me.lblCountTowns.Caption
=
CountTownFound
(
vbNullString
)
End
If
End
If
On
Error
GoTo
0
L_ExAddNewTown
:
'Libère l'objet de la mémoire
Set
oRS =
Nothing
Exit
Sub
L_ErrAddNewTown
:
'Message si erreur
MsgBox
Err
.Description
, vbExclamation
, Err
.Source
'Reprendre la suite...
Resume
L_ExAddNewTown
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Function
ThisTownExists
(
ByVal
Town As
String
, ByVal
PostalCode As
_
String
) As
Long
'Vérifie si la paire une commune et son CP existe déjà dans la base
Dim
oRS As
DAO.Recordset
Dim
SQL As
String
Dim
blnExists As
Boolean
Dim
SQLCheckTown As
String
Dim
m_strPostalCode As
Long
On
Error
GoTo
L_ErrThisTownExists
SQL =
"SELECT IDPostalCode FROM TBLPostalCodes WHERE Town = '"
&
Town &
"' AND PostalCode = '"
&
PostalCode &
"';"
Set
oRS =
CurrentDb.OpenRecordset
(
SQL, dbOpenSnapshot)
With
oRS
'Si un enregistrement est trouvé...
If
Not
.EOF
Then
ThisTownExists =
.Fields
(
0
).Value
.Close
End
With
On
Error
GoTo
0
L_ExThisTownExists
:
'Libère l'objet de la mémoire
Set
oRS =
Nothing
Exit
Function
L_ErrThisTownExists
:
MsgBox
Err
.Description
, vbExclamation
, Err
.Source
Resume
L_ExThisTownExists
End
Function
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
UpdateTownList
(
KeyAscii As
Integer
)
'Met à jour la liste des communes en fonction des caractères tapés dans la zone de texte
Dim
SQL As
String
Dim
SQLWhere As
String
Dim
strCityCount As
String
'Selon le sens de la recherche
Select
Case
cmdInvert.Caption
Case
CPTOWN_CAPTION
'Sens Code postal => Ville
Select
Case
KeyAscii
Case
8
, 48
To
57
'Backspace ou un nombre : acceptés
Case
Else
'Sinon rien
KeyAscii =
0
MsgBox
"Un caractère numérique est requis ici !"
, vbExclamation
, "Erreur de frappe"
m_strCurrentChars =
vbNullString
Exit
Sub
End
Select
If
KeyAscii =
8
Then
'Si on appuie sur Backspace alors on retire un caractère
On
Error
Resume
Next
m_strCurrentChars =
Left
$(
m_strCurrentChars, Len
(
m_strCurrentChars) -
1
)
On
Error
GoTo
0
Else
'Sinon la variable s'incrémente et prend le caractère tapé
m_strCurrentChars =
m_strCurrentChars &
Chr
(
KeyAscii)
End
If
'On applique alors le critère à la chaîne SQL
SQL =
"SELECT IDPostalCode, PostalCode, Town "
SQL =
SQL &
"FROM TBLPostalCodes "
SQLWhere =
"WHERE PostalCode Like '"
&
m_strCurrentChars &
"*' "
SQL =
SQL &
SQLWhere &
"ORDER BY PostalCode;"
'Et on rafraîchit la liste selon cette clause SQL
SetListOfCityProperties SQL, CPT_TOWN_COLWIDTH
Case
TOWNCP_CAPTION
Select
Case
KeyAscii
Case
8
, 32
'Backspace, un espace : acceptés
Case
39
, 45
'apostrophe ou tiret : refusés
KeyAscii =
0
MsgBox
"Les apostrophes et les tirets ne sont pas normalisés pour les noms des villes !"
&
_
vbCrLf
&
vbCrLf
&
"Rendez-vous ici pour plus d'infos :"
&
_
vbCrLf
&
"http://www.laposte.fr/sna/rubrique.php3?id_rubrique=87"
, vbExclamation
, "Erreur de frappe"
m_strCurrentChars =
vbNullString
'Voir "http://www.laposte.fr/sna/rubrique.php3?id_rubrique=87" pour plus d'infos
Case
65
To
90
'MAJUSCULE : acceptés
Case
97
To
122
'minuscule : acceptés donc converti en majuscule
KeyAscii =
KeyAscii -
32
'MAJUSCULE accentuées
Case
192
, 193
, 194
, 195
, 196
, 197
: KeyAscii =
65
'A
Case
199
: KeyAscii =
67
'C
Case
200
, 201
, 202
, 203
: KeyAscii =
69
'E
Case
204
, 205
, 206
, 207
: KeyAscii =
73
'I
Case
209
: KeyAscii =
78
'N
Case
210
, 211
, 212
, 213
, 214
: KeyAscii =
79
'O
Case
217
, 218
, 219
, 220
: KeyAscii =
85
'U
'minuscules accentuées
Case
224
, 225
, 226
, 227
, 228
, 229
: KeyAscii =
65
'97:a
Case
231
: KeyAscii =
67
'99:c
Case
232
, 233
, 234
, 235
: KeyAscii =
69
'101:e
Case
236
, 237
, 238
, 239
: KeyAscii =
73
'105:i
Case
241
: KeyAscii =
78
'110:n
Case
242
, 243
, 244
, 245
, 246
: KeyAscii =
79
'111:o
Case
249
, 250
, 251
, 252
: KeyAscii =
85
'117:u
Case
Else
'Sinon rien
KeyAscii =
0
MsgBox
"Un caractère alphabétique est requis ici !"
, vbExclamation
, "Erreur de frappe"
m_strCurrentChars =
vbNullString
Exit
Sub
End
Select
If
KeyAscii =
8
Then
'Si on appuie sur Backspace alors on retire un caractère
On
Error
Resume
Next
m_strCurrentChars =
Left
(
m_strCurrentChars, Len
(
m_strCurrentChars) -
1
)
On
Error
GoTo
0
Else
'Sinon la variable s'incrémente et prend le caractère tapé
m_strCurrentChars =
m_strCurrentChars &
Chr
(
KeyAscii)
End
If
'Si aucun caractère tapé alors on pose un astérisque de manière à ne pas provoquer une erreur
If
Len
(
m_strCurrentChars) =
0
Then
m_strCurrentChars =
"*"
'On applique alors le critère à la chaîne SQL
SQL =
"SELECT IDPostalCode, Town, PostalCode FROM TBLPostalCodes "
SQLWhere =
"WHERE Town Like '"
&
m_strCurrentChars &
"*' "
SQL =
SQL &
SQLWhere &
"ORDER BY Town;"
'Et on rafraîchit la liste selon cette clause SQL
SetListOfCityProperties SQL, TOWN_CPT_COLWIDTH
End
Select
'Et on compte le nombre de villes trouvées
Me.lblCountTowns.Caption
=
CountTownFound
(
SQLWhere)
'Si la zone ne contient que * on efface la variable (cas de KeyAscii = 8)
If
Len
(
m_strCurrentChars) Then
If
(
Asc
(
Left
$(
m_strCurrentChars, 1
)) =
42
) And
(
Len
(
m_strCurrentChars) =
1
) Then
m_strCurrentChars =
vbNullString
End
If
End
If
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Sub
SetListOfCityProperties
(
ByVal
Source As
String
, ColWidth As
String
)
'Rafraîchit la liste des villes
With
lboPCTowns
.ColumnCount
=
3
.BoundColumn
=
3
.RowSource
=
Source
.ColumnWidths
=
ColWidth
End
With
End
Sub
''' ---------------------------------------------------------------------------------------------------------------------------------------
Private
Function
CountTownFound
(
ByVal
SQLWhere As
String
) As
String
'Compte le nombre de villes correspondant au critère
Dim
oRS As
DAO.Recordset
Dim
SQL As
String
Dim
lngRowCount As
Long
On
Error
GoTo
L_ErrCountTownFound
'On ouvre le RecordSet sur le critère en cours...
SQL =
"SELECT COUNT(IDPostalCode) AS NBRows "
SQL =
SQL &
"FROM TBLPostalCodes "
&
SQLWhere
Set
oRS =
m_oDB.OpenRecordset
(
SQL, dbOpenSnapshot)
With
oRS
lngRowCount =
Nz
(
.Fields
(
0
).Value
, 0
)
'Si la variable est > 0
If
lngRowCount Then
CountTownFound =
lngRowCount &
" commune"
&
IIf
(
lngRowCount =
1
, " "
, "s "
) &
"trouvée"
&
_
IIf
(
lngRowCount =
1
, " "
, "s "
)
Else
CountTownFound =
"Aucune commune trouvée..."
End
If
.Close
End
With
On
Error
GoTo
0
L_ExCountTownFound
:
'Libère l'objet de la mémoire
Set
oRS =
Nothing
Exit
Function
L_ErrCountTownFound
:
MsgBox
Err
.Description
, vbExclamation
, Err
.Source
Resume
'L_ExCountTownFound
End
Function
Function
RemoveCharAccents
(
ByVal
TextToChange As
String
, Optional
ByVal
ToUpperCase As
Boolean
=
False
)
'Permet de substituer les accents d'un chaîne avec option MAJUSCULE
Const
CHARS_WITH_ACCENTS As
String
=
"ÀÁÂÃÄÅÈÉÊËÌÍÎÏÒÓÔÕÖØÙÚÛÜàáâãäåèéêëìíîïòóôõöøùúûüÿÑÇñç"
Const
CHARS_WITHOUT_ACCENTS As
String
=
"AAAAAAEEEEIIIIOOOOOOUUUUaaaaaaeeeeiiiioooooouuuuyNCnc"
Dim
C As
Integer
Dim
strChar As
String
Dim
strNoAccentChars As
String
Dim
strFinalString As
String
strFinalString =
TextToChange
strNoAccentChars =
CHARS_WITHOUT_ACCENTS
If
ToUpperCase Then
strNoAccentChars =
UCase
$(
CHARS_WITHOUT_ACCENTS)
End
If
For
C =
1
To
Len
(
CHARS_WITH_ACCENTS)
strChar =
Mid
$(
CHARS_WITH_ACCENTS, C, 1
)
If
InStr
(
1
, strFinalString, strChar, vbBinaryCompare) Then
strFinalString =
Replace
(
strFinalString, strChar, Mid
$(
strNoAccentChars, C, 1
))
End
If
Next
C
RemoveCharAccents =
IIf
(
ToUpperCase, UCase
(
strFinalString), strFinalString)
End
Function
Explications du déroulement
Le code est commenté ligne par ligne pour une meilleure compréhension...
Dans la fonction qui vérifie le caractère inscrit par sa valeur ASCII via l'argument KeyPress,
j'ai intentionnellement laissé le :
Case
39
, 45
'apostrophe ou tiret : refusés
KeyAscii =
0
MsgBox
"Les apostrophes et les tirets ne sont pas normalisés pour les noms des villes !"
&
_
vbCrLf
&
vbCrLf
&
"Rendez-vous ici pour plus d'infos :"
&
_
vbCrLf
&
"http://www.laposte.fr/sna/rubrique.php3?id_rubrique=87"
, vbExclamation
, "Erreur de frappe"
m_strCurrentChars =
vbNullString
tout simplement pour vous préciser que le tiret comme l'apostrophe sont interdits pour le nom des villes.
Tous les détails à ce sujet sont précisés ici : Service National de l'Adresse
Effectivement, dans la pratique et pour ce cas précis, j'aurais dû enlever ce "Case", l'interprétation aurait alors
été dans le "Case Else" et la valeur tapée aurait été ignorée (KeyAscii = 0).
Dans les gros traits, le formulaire et ses contrôles s'initialisent avec ce qui se déroule dans l'événement
Sur chargement (Load)...
À partir de l'instant où l'utilisateur inscrit un caractère dans la zone liste, l'événement
Sur touche activée (KeyPress) s'enclenche et appelle la procédure UpdateTownList() avec
le paramètre KeyAscii du même événement passé en argument à la procédure.
La liste se rafraîchit alors en conséquence de la condition WHERE qui s'exécute toujours avec
l'opérateur LIKE.
Le paramètre KeyPress est analysé à chaque frappe et la procédure adapte la valeur de la variable de
module m_strCurrentChars selon le sens (inversion) de la sélection.
Selon le bouton sur lequel l'utilisateur clique, il se passe un certain nombre de choses, mais à chaque fois, la liste
se rafraîchit et des contrôles se réinitialisent.
4. Mise en exécution▲
Une fois cela terminé, vous basculez de nouveau sur votre formulaire (Alt+Tab) ou bien vous quittez l'éditeur Visual Basic depuis le menu Fichier.
4-1. Affichage en mode formulaire▲
Vous enregistrez de manière à ne pas perdre votre travail et vous passez en Mode Formulaire une seconde fois.
Pour ce faire, appuyez sur F5 ou bien sur le bouton Mode formulaire...
À cet instant, lorsque vous écrivez dans la zone de texte, le rafraîchissement de la liste est déclenché.
La liste avec recherche par le nom de la commune... (remarquez l'intitulé du bouton cmdInvert)
La liste avec recherche par le numéro du code postal... (remarquez l'intitulé du bouton cmdInvert)
Dès que vous cliquez sur l'élément souhaité dans la liste, le bouton de validation se libère.
4-2. Ajout d'une ville...▲
Ainsi que je vous l'ai évoqué en début de ce tutoriel, le projet gère l'ensemble des communes de France.
Il est donc inutile d'en ajouter, mais j'ai prévu ce module parce le club qui utilisait l'application où était
implémenté ce module ne gérait que quelques villes, mais n'excluait pas l'ajout d'autres qui étaient hors de sa circonscription.
Le clic sur le bouton ajout, la procédure AddNewTown est appelée...
Remarque : Cette procédure contrôlera l'existence de la nouvelle entrée avant de la stocker...
Le message de confirmation est affiché...
La boîte de dialogue permet d'inscrire la ville...
Puis le code postal...
Une confirmation de l'entrée par un dernier message.
Si la ville est ajoutée, la liste se rafraîchira en conséquence.
Sinon, c'est qu'elle existe déjà, alors un message d'erreur informatif est affiché ;
4-3. Modification d'une ville▲
Il n'est pas prévu dans ce tutoriel de module de modification de la ville, en tout cas au niveau du formulaire.
Le mode opératoire est inscrit dans l'événement Click du bouton cmdUpdate où vous êtes tenu de créer
le formulaire approprié, celui-ci étant ouvert en mode Dialog avec la condition WHERE basée sur le critère
qui prend l'ID du code postal via la variable m_lngIDPostalCode...
5. Conclusion▲
Ce petit tutoriel permettra aux personnes exigeantes à l'égard des applications de gestion et nécessitant des options de
sélection évolutives et dynamiques de mettre en place ce type de formulaire...
Si le besoin tourne autour du même thème, tant mieux, vous n'aurez pas de grosses modifications à apporter, mais dans le cas contraire,
l'adaptation ne devrait pas poser trop de problèmes.
Je précise que j'ai volontairement modifié et simplifié quelques instructions pour que le code soit plus compréhensible pour tous...
De ce fait, les utilisateurs disposant d'un niveau avancé s'en apercevront et pourront adapter ces blocs selon leur propre méthode
de développement. Initialement, le projet exploitait une classe et des événements déclencheurs... Je me suis dit qu'il était
préférable que tout le monde puisse y accéder et j'ai adopté une rédaction plus simplifiée.
6. Sujets corrélatifs▲
Dans la FAQ du forum Access, vous pouvez trouver de nombreux sujets sur les zones de liste en cliquant ici.
7. Remerciements ▲
Je tiens à remercier toutes celles et ceux qui ont participé à la relecture de ce document en y incluant leurs remarques et en particulier :
Claude Leloup pour la correction,
dourouc05 pour le côté administratif,
Philippe Jochmans pour ses remarques relatives aux normes postales,
et
arkham46 pour le reste...
sans oublier Nono40 pour son outil permettant la rédaction des articles.