I. Avant-propos▲
Ce document a pour but de vous montrer comment concevoir un assistant de présaisie des données permettant à un panel d'utilisateurs de saisir les informations nécessaires à la mise à jour d'une base de données centrale située sur un serveur distant.
L'idée de ce tutoriel est née d'une situation existante pour laquelle j'ai été confronté à concevoir le même prototype d'outil que j'ai ici largement simplifié pour ne pas rendre la mise en œuvre trop complexe, notamment pour les débutants.
I-A. Niveau▲
Vous devez être relativement à l'aise avec Microsoft Access et connaître la conception avancée 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.
Ce tutoriel s'adresse plus particulièrement aux personnes qui possèdent déjà de bonnes notions concernant la gestion des événements avec Visual Basic sur Microsoft Access incluant la manipulation des données à l'aide de DAO.
I-B. Contact▲
Pour tous renseignements complémentaires, veuillez me contacter directement (Langue Argyronet) par MP.
Si votre question est, disons publique et technique, merci de poster un message dans le forum afin d'en faire partager le contenu…
I-C. Le projet proposé▲
Le projet abordé dans ce tutoriel est fourni sous forme de code source à titre d'exemple qui, une fois compilé, assemblé et interprété peut nécessiter des adaptations ou des modifications en rapport avec vos besoins.
Il est tout à fait possible que votre vision des choses à l'égard de la méthodologie employée au sein des différentes fonctions ou procédures soit différente de la mienne. Aussi, n'hésitez pas à proposer vos idées si vous pensez qu'elles peuvent apporter un plus à ce projet.
Notez que j'ai cherché avant tout à simplifier l'ensemble pour que tout un chacun puisse l'assimiler de façon aisée.
I-D. Contexte métier▲
Le projet présenté ici concerne des données relatives à des prestations de service supervisées par certains sites pour certaines communes. Ces prestations sont soumises à des contrats nantis d'une durée de validité. Libre à vous de vous en inspirer pour votre propre situation métier ou tout autre développement particulier.
II. Pourquoi ce projet▲
II-A. Contexte▲
Ce tutoriel est né, comme bien souvent en ce qui me concerne, de l'idée d'une réalisation professionnelle qui m'a été demandée et que j'ai souhaité partager avec vous, du fait je la trouve intéressante à exploiter.
J'espère que ce sera le cas pour vous…
Pour clarifier un peu les choses, le projet présenté ici ne peut être mis en application que si vous êtes dans une situation où un besoin de centralisation des données est nécessaire, et qu'il faut préparer les données du serveur d'une part, mais aussi que votre application de gestion soit exploitée par différents sites où l'activité professionnelle (pour le cas présenté ici) exerce sur les communes spécifiques, ces dernières étant associées ou non à des contrats.
Bien entendu, il faut considérer que la présentation de ce projet reste surtout et avant tout théorique, car je suis bien conscient qu'il est difficile de se mettre dans une situation identique à celle qui m'a poussé à créer cet assistant.
Ceci dit, l'objectif est surtout de comprendre les points culminants de ce tutoriel à savoir :
- voir comment il est relativement aisé d'enchaîner dynamiquement l'ouverture de formulaires à l'image de ce que vous rencontrez lorsque vous lancez un programme d'installation ;
- approfondir vos connaissances concernant plus particulièrement l'usage de procédures génériques associées à des événements, qui évite ainsi la redondance de code et donc par voie de conséquence, une maintenance plus aisée ;
- voir également comment il est possible de vérifier qu'un sous-formulaire est complété comme attendu au moment de la saisie d'une part, et au moment de la validation globale d'autre part. J'entends par validation globale, les comportements associés au passage à l'écran précédent ou à l'écran suivant qui se traduit par le fait de conserver ou non le contenu (confirmé par un message).
D'une manière générale, du fait que cet assistant est destiné à des non-informaticiens, on fait en sorte de rendre les messages affichés qui concernent la sauvegarde, le soient de manière optimiste. En d'autres termes, si l'utilisateur n'est pas en mesure de renseigner une valeur, qu'il ne soit pas obligé de le faire.
II-B. De quoi avez-vous besoin ?▲
Pour mettre en œuvre ce projet, vous n'avez besoin que de Microsoft Access dans une version supérieure ou égale à la version 2000. Il n'y a pas de référence particulière autre que celles définies par défaut dans Microsoft Access lors de la création de nouveaux projets.
Libre à vous de préférer DAO 3.6 ou le nouveau moteur existant depuis la version 2007.
III. Présentation du projet▲
Le projet est ici et expressément une base de données Access non scindée. Je précise cela, car il est plus facile de distribuer une application dans cette configuration d'une part, et cela évite les problèmes d'installation d'autre part. Par ailleurs, du fait du côté volatil de l'application si je puis dire, il n'est pas nécessaire de s'investir dans une structure frontale/dorsale.
III-A. Structure fonctionnelle des formulaires▲
Dans le projet présenté ici, les éléments prépondérants sont bien entendu les formulaires. Ils sont au nombre de onze. Parmi ces formulaires, figure un formulaire d'accueil qui propose et énumère ce qui va être fait et un formulaire de fin qui récapitule la saisie.
Entre les deux, figurent tous les autres formulaires de saisie des données utilisateur qu'ils soient formulaires simples ou continus.
Concernant les formulaires de saisie, chacun d'entre eux est pourvu de boutons qui permettent de créer ou de supprimer un enregistrement, et également, d'accéder au formulaire précédent ou au formulaire suivant.
Pour chacun des formulaires qui contiennent des sous-formulaires, sont présentes deux étiquettes qui pour l'une, permet de savoir si le formulaire et dûment complété ou non et pour l'autre, le nombre d'enregistrements existants.
Il est rappelé ici que les écrans doivent être construits de telle sorte à ce qu'ils soient compris par des personnes qui n'ont pas l'habitude d'utiliser l'outil informatique et à qui l'on demande de saisir des informations avec un mode qualifié de simple.
IV. Les tables du projet▲
Au niveau des tables, du fait que ce projet concerne dans son côté métier l'exploitation de prestations de services par rapport à des communes, certaines tables contiennent des valeurs prédéfinies.
Dans l'exemple présenté, trois tables contiennent respectivement l'ensemble des communes, des départements et des régions pour la France.
Les autres tables contiennent des valeurs qui concernent les types de contrats ou les privilèges utilisateur. Si vous décidez de mettre en œuvre ce projet dans son intégralité, il vous faudra sans doute procéder à des adaptations selon votre propre contexte professionnel.
IV-A. La structure des tables de données fixes▲
IV-A-1. tbl_ListeDepartements (les départements*)▲
Cette table possède trois champs :
- le champ IDDepartement de type Texte limité à trois caractères ;
- le champ Departements de type Texte limité à soixante-quatre caractères ;
- le champ IDRegion de type numérique Entier long .
La clé primaire est affectée au champ IDDepartement.
IV-A-2. tbl_ListeDesCommunes (Les communes*)▲
Cette table possède cinq champs :
- le champ IDCommune représenté par un identifiant de type Numérotation automatique ;
- le champ CodeINSEE de type Texte limité à cinq caractères ;
- le champ NomCommune de type Texte limité à soixante-quatre caractères ;
- le champ CodePostal de type Texte limité à cinq caractères ;
- le champ IDDepartement de type Texte limité à trois caractères .
La clé primaire est affectée aux champs CodeINSEE + NomCommune (CodeINSEE indexé sans doublons).
IV-A-3. tbl_ListeDesRegions (les régions*)▲
Cette table possède deux champs :
- le champ IDRegion de type Numérotation automatique ;
- le champ Region de type Texte limité à soixante-quatre caractères.
La clé primaire est affectée au champ IDRegion.
Du fait que ces tables (*) sont facultatives, dans la mesure où elles ne sont utilisées dans ce projet qu'à titre d'exemple, vous n'êtes pas obligé de les mettre en œuvre…
IV-A-4. tbl_Privileges (les privilèges utilisateur**)▲
Cette table possède trois champs :
- le champ IDPrivilege de type Numérique Entier long ;
- le champ CodePrivilege de type Texte limité à cinq caractères ;
- le champ LibellePrivilege de Texte limité à soixante-quatre caractères.
La clé primaire est affectée aux champs IDPrivilege + CodePrivilege tous indexés sans doublon.
IV-A-5. tbl_TypesContrat (les types de contrats**)▲
IV-B. La structure des tables de données utilisateur▲
IV-B-1. tblCommunesSite (les associations communes et site)▲
Cette table possède trois champs :
- le champ IDCommune de type Numérique Entier long ;
- le champ NoSite de type Numérique Entier long ;
- le champ IDDepartement de type Texte limité à trois caractères.
La clé primaire est affectée aux champs IDCommune + NoSite tous indexés avec doublons.
IV-B-2. tblContratCommunes (les associations contrat et communes)▲
Cette table possède deux champs :
- le champ IDCommune de type Numérique Entier long ;
- le champ IDContrat de type Texte limité à cinq caractères.
La clé primaire est affectée aux champs IDCommune + IDContrat tous indexés avec doublons.
IV-B-3. tblContrats (Les contrats)▲
Cette table possède cinq champs :
- le champ IDCommune représenté par un identifiant de type Numérotation automatique ;
- le champ IDContrat de type Texte limité à cinq caractères ;
- le champ LibelleContrat de type Texte limité à 128 caractères ;
- le champ DateFin de type Date ;
- le champ IDTypeContrat de type Numérique Entier long.
La clé primaire est affectée au champ IDContrat indexé sans doublon.
IV-B-4. tblPersonnel (Le personnel)▲
Cette table possède six champs :
- le champ IDPersonne représenté par un identifiant de type Numérotation automatique ;
- le champ NomFamille Texte limité à soixante-quatre caractères ;
- le champ Prenom de type Texte limité à soixante-quatre caractères ;
- le champ EMAIL de type Texte limité à soixante-quatre caractères ;
- le champ Login de type Texte limité à soixante-quatre caractères ;
- le champ NoSite de type Numérique Entier long ;
- le champ IDPrivileges de type Numérique Entier long.
La clé primaire est affectée aux champs NomFamille + Prenom + NoSite tous indexés avec doublons.
IV-B-5. tblSites (Les sites)▲
Cette table possède quatre champs :
- le champ NoSite de type Numérique Entier long ;
- le champ LibelleSite de type Texte limité à soixante-quatre caractères ;
- le champ IDCommune de type Numérique Entier long ;
- le champ DateOuverture de type Date.
La clé primaire est affectée aux champs NoSite + LibelleSite
NoSite est indexé sans doublon et LibelleSite est indexé avec doublons.
IV-C. Données des tables▲
Pour simplifier les choses pour le développeur, j'ai adopté intentionnellement une convention de nommage pour les tables qui contiennent des données prédéfinies avec un caractère underscore (_).
Rappel
Celles qui possèdent un astérisque(*) sont des tables facultatives puisqu'elles ne contiennent que les régions, les départements et les communes, et concernent plus particulièrement ce projet.
Celles qui possèdent deux astérisques(**) sont des tables directement liées au projet si l'on s'en réfère à l'aspect métier de ce dernier.
Toutes les autres tables sont dédiées à recevoir des données saisies par les utilisateurs. Ce seront ces tables qui seront remises à blanc, si toutefois l'utilisateur en fait la demande au chargement du projet. Nous verrons cela un petit peu plus bas dans le processus d'utilisation des formulaires.
Exemple de données dans les tables
IV-C-1. Liste des départements (tbl_ListeDepartements)▲
|
IDDepartement |
Departement |
IDRegion |
|
01 |
Ain |
23 |
|
02 |
Aisne |
20 |
|
03 |
Allier |
3 |
|
04 |
Alpes-de-Haute-Provence |
22 |
|
05 |
Hautes-Alpes |
22 |
|
06 |
Alpes-Maritimes |
22 |
|
… |
… |
… |
IV-C-2. Liste des régions (tbl_ListeDesRegions)▲
|
IDRegion |
Region |
|
1 |
Alsace |
|
2 |
Aquitaine |
|
3 |
Auvergne |
|
4 |
Basse-Normandie |
|
5 |
Bourgogne |
|
… |
… |
Liste des communes (tbl_ListeDesCommunes)
|
IDCommune |
Code INSEE |
Commune |
Code Postal |
IDDepartement |
|
447 |
98611 |
ALO |
98610 |
98 |
|
603 |
98711 |
ANAA |
98760 |
98 |
|
1307 |
98712 |
ARUE |
98701 |
98 |
|
1308 |
98713 |
ARUTUA |
98761 |
98 |
|
3007 |
98801 |
BELEP |
98811 |
98 |
|
4276 |
98714 |
BORA BORA |
98730 |
98 |
|
4510 |
98802 |
BOULOUPARI |
98812 |
98 |
|
4524 |
98803 |
BOURAIL |
98870 |
98 |
|
5855 |
98804 |
CANALA |
98813 |
98 |
|
10485 |
98805 |
DUMBEA |
98837 |
98 |
|
11481 |
98715 |
FAAA |
98704 |
98 |
|
11508 |
98716 |
FAKARAVA |
98790 |
98 |
|
… |
… |
… |
… |
… |
IV-C-3. Liste des types de contrats (tbl_TypesContrat)▲
|
IDTypeContrat |
Type de contrat |
|
1 |
Contrat communal |
|
2 |
Contrat intercommunal |
|
3 |
Contrat fictif |
IV-C-4. tbl_Privileges▲
|
IDPrivilege |
Code Privilège |
Libellé Privilèges |
|
1 |
ADMR |
Administrateur Régional |
|
2 |
ADMS |
Administrateur du Site |
|
3 |
USERA |
Utilisateur Agence |
|
4 |
GUEST |
Invité ou Externe |
V. Le schéma relationnel de l'application▲
Bien qu'il ne soit pas obligatoire d'en élaborer un, le schéma relationnel a été mis en place dans ce projet. Il est représenté par l'illustration ci-dessous :
Cela permet, en tout cas ici, de mieux appréhender la structure…
Vous pouvez remarquer qu'il y a deux jeux de tables distincts :
- un jeu de tables concernant les données utilisateur ;
- un jeu de tables concernant uniquement les communes, leur département et leur région.
VI. Conception des formulaires▲
Il existe autant de formulaires qu'il y a d'étapes.
Dans l'exemple présenté ici, je me suis limité à cinq écrans qualifiés à proprement parler de saisies, auxquels j'ai ajouté un formulaire d'accueil et un formulaire final récapitulatif.
Ce projet comporte donc onze formulaires :
- le premier et le dernier sont les formulaires de début et de fin ;
- trois d'entre eux possèdent un sous-formulaire ;
- un autre est un sous-formulaire de type pop-up ;
- deux autres sont des formulaires simples.
VI-A. Les différents formulaires (en mode utilisation)▲
Dans cet assistant, parmi les cinq formulaires de saisie, se trouvent des formulaires dits « simples ». Les autres seront élaborés avec une structure Parent/Enfant, autrement dit, un formulaire principal et un sous-formulaire, ce dernier possédant une structure continue.
VI-A-1. Le formulaire d'accueil▲
VI-A-2. Le formulaire de saisie du site▲
VI-A-3. Le formulaire de saisie du personnel▲
VI-A-4. Le formulaire de saisie des communes▲
VI-A-5. Le formulaire de saisie des contrats▲
VI-A-6. Le formulaire de saisie de l'association Contrat-Communes▲
VI-A-7. Le formulaire de fin▲
VII. Description structurelle des formulaires▲
VII-A. La source de données formulaires▲
Tous les formulaires dédiés à la saisie se voient affecter leur source de données dynamiquement par code. Il est rappelé ici que l'objectif de ce tutoriel est de vous apprendre à minimiser l'écriture du code d'une part, et de faire en sorte que chaque feuille de code représentée par la classe des formulaires soit la plus générique possible.
Considérant que pratiquement tous les écrans restent identiques selon leur type (simple ou continu), il était important de faire en sorte que la source de données bénéficie des mêmes privilèges.
Vue éclatée des formulaires (description des contrôles)
Pour faciliter la mise en œuvre des formulaires, les paragraphes qui vont suivre vous présentent une photographie du formulaire en Mode création tel qu'il est vu à l'écran lorsqu'il s'agit d'un formulaire simple, et en vue éclatée lorsqu'il s'agit d'un sous-formulaire.
En effet, du fait que certains contrôles sont volontairement cachés en mode formulaire, il est plus aisé de se rendre compte de la façon dont l'ensemble des contrôles a été agencé et disposé.
VII-B. Les formulaires simples ▲
Ils sont au nombre de quatre :
deux formulaires sans données saisies et deux avec données saisies par l'utilisateur.
VII-B-1. Formulaire sans données utilisateur▲
Ces formulaires ne sont pas structurés de la même façon.
Ils sont essentiellement composés de contrôles étiquette informative et de boutons de navigation.
Ces deux formulaires sont :
- le formulaire d'accueil : frmStart ;
- le formulaire de fin : frmEnd.
VII-B-2. Formulaire avec données utilisateur▲
Ces formulaires sont tous structurés de la même façon.
Ils possèdent tous un contrôle étiquette permettant d'affecter dynamiquement le contenu du titre qui est alimenté directement au moment du chargement.
Juste au-dessous de celui-ci se trouvent les contrôles des champs de la source.
Dans la partie basse de chacun d'eux, se trouvent quatre boutons de commande :
- un bouton Supprimer qui recevra la légende Sauvegarder en cours de saisie ;
- un bouton Ajouter qui recevra la légende Annuler en cours de saisie ;
- un bouton intitulé Précédent qui permet de revenir à l'écran précédent ;
- et un bouton Suivant qui permet de passer à l'écran suivant.
Au niveau des propriétés
Vous définirez les propriétés ci-après comme suit :
- Cycle à « Enregistrement en cours » ; cette propriété permet de spécifier ce qui se produit lorsque vous appuyez sur la touche TAB pendant que le dernier contrôle d'un formulaire dépendant est activé ;
- Menu Contextuel à « Non » ; cette propriété permet de spécifier s'il doit être affiché lorsque vous cliquez sur un objet d'un formulaire à l'aide du bouton droit de la souris ;
- Type Recordset à « Feuille de réponse dynamique » pour permettre de spécifier quel genre de jeu d'enregistrements est disponible pour le formulaire ;
- Propriété Remarque (Tag) définie à Null ou NotNull selon les champs pour lesquels vous souhaitez ne pas considérer la nullité comme bloquante.
Explications
Tous les champs des formulaires simples voient leur propriété
Tag
définie à
Null
ou
NotNull
:
cette propriété est utilisée dans une fonction qui contrôle le bon contenu des champs.
Voir cette rubrique pour plus d'informations ici.
VII-C. Les formulaires avec un sous-formulaire ▲
VII-C-1. Les formulaires parents▲
Ils sont au nombre de trois…
Ces formulaires sont tous structurés de la même façon.
Ils possèdent tous un contrôle étiquette permettant d'affecter dynamiquement le titre, le sujet et juste au-dessous de ceux-ci, se trouvent des zones de texte cachées que j'ai intentionnellement coloriées en jaune avec une police de couleur rouge.
Attention
Ces champs voient leur propriété Visible dépendante de la constante SHOW_HIDDEN_CONTROLS.
En leur milieu se trouve un sous-formulaire pour lequel il n'y a volontairement pas de notion Champs pères et Champs fils.
Les sous-formulaires portent toujours le même nom (sfrmDataWizard), et ce, quel que soit leur formulaire Parent.
En dessous de leur sous-formulaire se trouvent deux contrôles étiquette, l'un spécifiant le nombre d'enregistrements inscrits dans le sous-formulaire, l'autre spécifiant par effet de clignotement que la ligne est complète ou incomplète dans le sous-formulaire.
Cette dernière étiquette clignote grâce à l'événement Timer.
Dans la partie basse de chacun des formulaires parents sont dessinés quatre boutons de commande :
- un bouton Supprimer qui recevra la légende Sauvegarder en cours de saisie ;
- un bouton Ajouter qui recevra la légende Annuler en cours de saisie ;
- un bouton Précédent qui permet de revenir à l'écran précédent ;
- et un bouton Suivant qui permet de passer à l'écran suivant.
VII-C-2. Les formulaires enfants (ou sous-formulaire)▲
Chacun des sous-formulaires possède dans la partie droite de sa zone de détail, une case à cocher dont la formule calcule en permanence (donc au moment de la saisie) le fait que la ligne est complétée ou non.
On considère que la ligne est complétée à partir du moment où la formule stockée dans le champ caché (de couleur jaune) nommé txtCheckdata renvoie bien le résultat attendu.
Ce champ possède une formule appellant une fonction écrite en VBA qui calcule la somme de la nullité des champs de données avec leur type associé via la fonction Nz().
La fonction en elle-même retourne 0 ou 1 selon que le champ est nul ou non, et ce, quel que soit son type. Selon le nombre de champs où la valeur est requise, une certaine somme est attendue.
Ainsi, pour la table du personnel du site, l'ensemble des six champs est obligatoire ; cela implique que la formule doit renvoyer tout simplement 6.
La case à cocher prendra la valeur True ou False selon ce résultat.
Exemple de formule pour un sous-formulaire :
la zone de texte cachée « txtCheckdata »
=(NullData([NomDuChamp1];4)+NullData([NomDuChamp2];10)+NullData([NomDuChamp3];10))et la case à cocher associée « chkCompleted »
=([txtCheckdata]=3)la fonction NullData() sera expliquée un peu plus loin dans ce tutoriel.
Dans la partie basse de chacun des sous-formulaires, au niveau du pied de formulaire, se trouvent des zones de texte cachées dont deux d'entre elles calculent en permanence le nombre d'enregistrements pour l'une et la valeur de la case à cocher pour l'autre.
Les autres zones de texte cachées reprennent selon la source de données, les champs utilisés pour accéder à l'enregistrement, et donc leur identifiant d'une part et leur libellé d'autre part ; les valeurs de ces champs sont utilisées au niveau du formulaire parent pour pouvoir afficher le contenu dans un message, notamment lorsqu'il s'agit de supprimer la ligne.
Remarque
Chacune des zones de texte cachées situées dans le pied de formulaire a une propriété Visible définie à False et une hauteur fixée à 0.
VII-C-3. Au niveau des propriétés▲
Vous définirez les propriétés ci-après comme suit :
- Cycle à « Tous les enregistrements » ; cette propriété permet de spécifier ce qui se produit lorsque vous appuyez sur la touche TAB pendant que le dernier contrôle d'un formulaire dépendant est activé ;
- Menu Contextuel à « Non » ; cette propriété permet de spécifier s'il doit être affiché lorsque vous cliquez sur un objet d'un formulaire à l'aide du bouton droit de la souris ;
- Type Recordset à « Feuille de réponse dynamique » pour permettre de spécifier quel genre de jeu d'enregistrements est disponible pour le formulaire.
VIII. Description détaillée des formulaires▲
VIII-A. Le formulaire d'accueil▲
Ce formulaire se nomme frmStart.
Ce formulaire dépourvu de données est composé principalement de contrôles étiquette dédiés à présenter quelque peu l'interface, mais aussi :
- d'une image représentant un logo par exemple ;
- d'une case à cocher permettant de remettre à blanc les données ;
- et de boutons de commande, un pour quitter, l'autre pour démarrer.
Le titre de ce formulaire est statique du fait que c'est le formulaire d'accueil.
VIII-B. Le formulaire de saisie du site▲
Ce formulaire se nomme frmDataWizard1.
Il s'agit du formulaire qui contient les coordonnées du site.
Ce formulaire possède :
- un contrôle de type étiquette présentant succinctement l'objet du formulaire ;
- trois zones de texte et deux listes déroulantes ;
- et trois boutons de commande.
La première zone de texte définira le numéro du site, la deuxième, le libellé et la troisième, la date d'ouverture du site.
La zone de liste déroulante indépendante permet de lister les départements et leur région et possède le code événementiel associé pour pouvoir filtrer les communes correspondant à celui sélectionné.
La zone de liste des communes contient l'ensemble des communes issues de la table via une instruction SQL filtrée sur le critère précité.
Dans la partie basse du formulaire se trouvent trois boutons :
- un bouton intitulé Effacer qui permet de remettre à blanc la saisie en cours ;
- un bouton intitulé Précédent qui permet de revenir à l'écran précédent ;
- et un bouton Suivant qui permet de passer à l'écran suivant.
VIII-C. Le formulaire de saisie du personnel▲
Ce formulaire est nommé : frmDataWizard2.
Il s'agit du formulaire permettant de saisir les employés du site.
La partie haute du formulaire contient les cinq champs cachés qui récupèrent les valeurs du pied de formulaire du sous-formulaire.
Juste en dessous du sous-formulaire, se trouvent les deux étiquettes qui calculent par programme le nombre d'employés enregistrés d'une part et si la ligne en cours de saisie dans le sous-formulaire est complète d'autre part.
|
N° |
Champ |
Formule ou Source |
Description |
|
|
txtTotalRows |
=[frmDataWizard2_sub]![txtCountRows] |
Valeur de txtCountRows* |
|
|
txtNoSite |
=[frmDataWizard2_sub]![txtNoSite] |
Valeur de txtNoSite* |
|
|
txtIDPersonne |
=[frmDataWizard2_sub]![txtIDPersonne] |
Valeur de txtIDPersonne* |
|
|
txtNomPrenom |
=[frmDataWizard2_sub]![txtNomPrenom] |
Valeur de txtNomPrenom* |
|
|
txtRowCompleted |
=[frmDataWizard2_sub]![txtCompleted] |
Valeur de txtCompleted* |
* issues des champs du sous-formulaire de la zone pied de formulaire (les champs portent le même nom pour plus de commodité).
VIII-C-1. Le sous-formulaire de saisie du personnel▲
Ce formulaire est nommé : frmDataWizard2_sub.
Il possède tous les champs de la table du personnel (tblPersonnel).
Dans la partie En-tête sont posées des étiquettes correspondant à chacun des champs de la table et dans la partie Détail, les champs eux-mêmes.
Dans la zone de détail se trouvent les champs cachés :
-
la zone de texte « txtCheckdata » dont la formule associée est :
Formule de txtCheckdataSélectionnez=(NullData([NoSite];4)+NullData([Prenom];10)+NullData([NomFamille];10)+NullData([EMAIL];10)+NullData([LOGIN];10)+NullData([IDPrivileges];4)) - la case à cocher « chkCompleted » dont la formule associée est :
=([txtCheckdata]=6)Dans le pied du formulaire se trouvent les champs cachés dont les formules permettent de comptabiliser le nombre d'employés, de renvoyer l'identifiant, le libellé (ici nom et prénom) et la complétion de l'enregistrement recevant le focus (donc en cours).
|
N° |
Champ |
Formule ou Source |
Description |
|
|
txtCheckdata |
=NullData(de chaque champ) |
Calcule la somme des Nz()+Nz()+… |
|
|
txtCountRows |
=Compte([IDPersonne]) |
Compte le nombre d'enregistrements |
|
|
txtNoSite |
NoSite |
Renvoie le numéro du site en cours |
|
|
txtIDPersonne |
IDPersonne |
Renvoie l'ID de l'employé en cours |
|
|
txtNomPrenom |
=[Prenom] & " " & [NomFamille] |
Renvoie le nom et le prénom en cours |
|
|
txtCompleted |
=[chkCompleted] |
Renvoie la valeur de la case à cocher |
VIII-D. Le formulaire de saisie des communes▲
Ce formulaire est nommé : frmDataWizard3.
Il s'agit du formulaire permettant de sélectionner les communes exploitées par le site.
À l'instar du formulaire de saisie des employés, celui-ci possède quatre champs cachés dont les formules restent identiques à ceci près qu'elles ne pointent pas vers les mêmes champs, excepté bien entendu celle qui compte le nombre de lignes et celle qui retourne la complétion de l'enregistrement en cours.
|
N° |
Champ |
Formule ou Source |
Description |
|
|
txtTotalRows |
=[frmDataWizard3_sub]![txtCountRows] |
Valeur de txtCountRows* |
|
|
txtIDCommune |
=[frmPerimeter3_sub]![txtIDCommune] |
Valeur de txtIDCommune* |
|
|
txtCommune |
=[frmPerimeter3_sub]![txtCommune] |
Valeur de txtCommune* |
|
|
txtRowCompleted |
=[frmDataWizard3_sub]![txtCompleted] |
Valeur de txtCompleted* |
* issues des champs du sous-formulaire de la zone pied de formulaire (les champs portent le même nom pour plus de commodité).
VIII-D-1. Le sous-formulaire de saisie des communes▲
Ce formulaire est nommé : frmDataWizard3_sub.
Il possède tous les champs de la table du personnel (tblCommunesSite).
Dans la partie En-tête seront posées des étiquettes correspondant à chacun des champs de la table et dans la partie Détail, les champs eux-mêmes.
Dans la zone de détail se trouvent les champs cachés :
-
la zone de texte « txtCheckdata » dont la formule associée est :
Formule de txtCheckdataSélectionnez=(NullData([NoSite];4)+NullData([IDCommune];4)) - la case à cocher « chkCompleted » dont la formule associée est :
=([txtCheckdata]=2)Dans le pied du formulaire se trouvent les champs cachés dont les formules permettent de comptabiliser le nombre de communes, de renvoyer l'identifiant, le libellé (ici nom de la commune) et la complétion de l'enregistrement recevant le focus (donc en cours).
|
N° |
Champ |
Formule ou Source |
Description |
|
|
txtCheckdata |
=NullData(de chaque champ) |
Calcule la somme des Nz()+Nz()+… |
|
|
txtCountRows |
=Compte([IDPersonne]) |
Compte le nombre d'enregistrements |
|
|
txtIDCommune |
IDCommune |
Renvoie l'ID de la commune en cours |
|
|
txtCommune |
=[IDCommune].[column](1) |
Valeur de txtNomCommune |
|
|
txtCompleted |
=[chkCompleted] |
Valeur de la case à cocher |
VIII-E. Le formulaire de saisie des contrats▲
Ce formulaire est nommé : frmDataWizard4_Popup.
Il s'agit du formulaire qui contient les différents contrats.
Ce formulaire possède :
- quatre zones de texte et une liste déroulante pour les données propres au contrat ;
- cinq boutons de commande.
La première zone de texte définira le numéro du contrat, la seconde, son libellé, et les deux dernières, respectivement la date de début et la date de fin du contrat.
La zone de liste déroulante permet de lister les types de contrats.
Dans la partie basse du formulaire se trouvent les cinq boutons : - un bouton intitulé Supprimer qui permet de remettre à blanc la saisie en cours ;
- un bouton intitulé Ajouter qui permet d'ajouter un nouveau contrat ;
- un bouton intitulé Voir les contrats déjà saisis qui permet de visualiser tous les contrats déjà enregistrés ;
- un bouton intitulé Précédent qui permet de revenir à l'écran précédent ;
- et un bouton Suivant qui permet de passer à l'écran suivant.
|
|
Le champ IDTypeContrat représenté par une Zone de liste déroulante est attaché à la table tbl_TypesContrat à l'aide d'une requête : |
SELECT tbl_TypesContrat.IDTypeContrat, tbl_TypesContrat.TypeContrat
FROM tbl_TypesContrat;Pour plus de commodité, j'ai stocké cette clause SQL dans une requête nommée :
qry_CBOListeTypesDeContrat.
VIII-E-1. Le formulaire « popup » des contrats▲
Ce formulaire est nommé : frmDataWizard4_Popup
Il s'agit du formulaire qui contient l'ensemble des contrats que vous avez déjà saisis.
Il est à noter que le bouton d'accès à ce formulaire reste grisé tant que le nombre de contrats est inférieur ou égal à un.
Ce formulaire est construit en mode continu avec une structure En-tête et Pied de formulaire.
- Dans la partie En-tête seront posées des étiquettes correspondant à chacun des champs de la table et dans la partie Détail, les champs eux-mêmes.
- Dans le Pied du formulaire se trouvent :
- Un champ avec une formule qui permet de comptabiliser le nombre de contrats existants ;
- Et sur la droite, un bouton permettant de refermer la fenêtre.
Dans ce formulaire, tous les contrôles sont verrouillés là où la propriété Arrêt tabulation est définie à False.
En haut à gauche et de façon invisible, se trouve un champ nommé txtFocus et qui reçoit le focus en permanence.
La formule du champ de comptabilisation est la suivante :
=Compte([IDContrat]) & " contrats enregistrés."VIII-F. Le formulaire de saisie de l'association Contrat-Communes▲
Ce formulaire est nommé : frmDataWizard5.
Il s'agit du formulaire permettant de sélectionner les communes exploitées par le site.
À l'instar du formulaire de saisie des employés, celui-ci possède quatre champs cachés dont les formules restent identiques à ceci près qu'elles ne pointent pas vers les mêmes champs, excepté bien entendu celle qui compte le nombre de lignes et celle qui retourne la complétion de l'enregistrement en cours.
|
N° |
Champ |
Formule ou Source |
Description |
|
|
txtTotalRows |
=[frmDataWizard5_sub]![txtCountRows] |
Valeur de txtCountRows* |
|
|
txtIDContrat |
=[frmPerimeter5_sub]![IDContrat] |
Valeur de txtIDContrat* |
|
|
txtIDCommune |
=[frmPerimeter5_sub]![txtIDCommune] |
Valeur de txtIDCommune* |
|
|
txtNomCommune |
=[frmPerimeter5_sub]![txtNomCommune] |
Valeur de txtNomCommune* |
|
|
txtRowCompleted |
=[frmDataWizard5_sub]![txtCompleted] |
Valeur de txtCompleted* |
* issues des champs du sous-formulaire de la zone pied de formulaire (les champs portent le même nom pour plus de commodité).
VIII-F-1. Le sous-formulaire de saisie de l'association Contrat-Communes▲
Ce formulaire est nommé : frmDataWizard5_sub.
Ce formulaire possède tous les champs de la table du personnel (tblContratCommunes).
Dans la partie En-tête seront posées des étiquettes correspondant à chacun des champs de la table et dans la partie Détail, les champs eux-mêmes.
Dans la zone de détail se trouvent les champs cachés :
-
la zone de texte « txtCheckdata » dont la formule associée est :
Formule de txtCheckdataSélectionnez=(NullData([IDContrat];10)+NullData([IDCommune];4)) - la case à cocher « chkCompleted » dont la formule associée est :
=([txtCheckdata]=2)Dans le pied du formulaire se trouvent les champs cachés dont les formules permettent de comptabiliser le nombre d'employés, de renvoyer l'identifiant, le libellé (ici nom de la commune et N° du contrat) et la complétion de l'enregistrement recevant le focus (donc en cours).
|
N° |
Champ |
Formule ou Source |
Description |
|
|
txtCheckdata |
=NullData(de chaque champ) |
Calcule la somme des Nz()+Nz()+… |
|
|
txtCountRows |
=Compte([IDContrat]) |
Compte le nombre d'enregistrements |
|
|
txtIDContrat |
IDContrat |
Renvoie l'ID du contrat en cours |
|
|
txtIDCommune |
IDCommune |
Renvoie l'ID de la commune en cours |
|
|
txtNomCommune |
=[IDCommune].[column](1) |
Renvoie le nom de la commune en cours |
|
|
txtCompleted |
=[chkCompleted] |
Renvoie la valeur de la case à cocher |
VIII-G. Le formulaire de fin▲
C'est le dernier formulaire qui s'affiche pour résumer les étapes omises (s'il y en a) et pour clore l'application après confirmation.
Le projet a été élaboré pour être ouvert autant de fois que nécessaire de manière à ce que l'enrichissement des informations puisse se faire sur plusieurs jours.
Ce formulaire, tout comme le formulaire d'accueil, est composé essentiellement de contrôles type étiquette et trois boutons de commande.
Deux de ces étiquettes sont alimentées par code, les autres possèdent un texte statique.
Dans la partie basse du formulaire se trouvent les trois boutons :
- un bouton intitulé Début qui permet de revenir au début des étapes ;
- un bouton intitulé Précédent qui permet de revenir à l'écran précédent ;
- et un bouton Terminer qui permet de fermer l'application après confirmation.
|
N° |
Contrôle |
Source |
Description |
|
|
lblStepStatus |
Code |
Affiche la mention de complétion selon les étapes |
|
|
lblStepList |
Code |
Affiche la liste des étapes qui ont été omises |
IX. Démarrage du projet▲
IX-A. Chargement par macro exemple 1▲
Dans le cas présenté ici, le projet démarre à l'aide d'une macro AutoExec qui pilote l'ouverture du formulaire d'accueil via l'action OuvrirFormulaire, le formulaire étant frmStart (formulaire d'accueil) et le mode de fenêtre étant Boîte de dialogue.
IX-B. Chargement par macro exemple 2▲
Autre possibilité, vous pouvez encore appeler une fonction personnalisée via une macro AutoExec qui ouvrira davantage de possibilités qu'offrent les actions des macros. Vous utiliserez alors la méthode OpenForm de l'objet DoCmd avec le paramètre acDialog.
IX-C. Chargement par propriété intrinsèque de la base de données▲
Autre solution, vous pouvez tout aussi bien définir les propriétés de démarrage de la base de données et sélectionner ce même formulaire.
X. Description des événements au sein des formulaires▲
Nous allons décrire ici les procédures événementielles qui se trouvent au sein des différentes classes de formulaire.
Pour chaque feuille, l'Option Explicit a été affectée et ne doit pas être supprimée.
Cette option se définit d'elle-même si vous cochez la case de l'éditeur VBE.
Tous les formulaires des étapes (ceux où l'utilisateur saisit des données) possèdent à peu de choses près, le même jeu de constantes dans l'en-tête :
|
Constante |
Description |
|
PREVIOUS_FORM_ID |
Index du formulaire précédent |
|
NEXT_FORM_ID |
Index du formulaire suivant |
|
TARGET_TABLE |
Nom de la table source |
|
FIELD_COLLECTION |
Chaîne de tableau contenant la liste des champs utilisés |
|
RECORD_SOURCE |
Clause SELECT basée sur TARGET_TABLE |
|
DELETE_RECORD_SQL |
Clause DELETE basée sur TARGET_TABLE |
|
WHERE_CONDITION |
Clause WHERE de la clause DELETE_RECORD_SQL |
|
FORM_CAPTION |
Légende de la barre de titre du formulaire |
|
MAX_STEP_WIZ |
Constante globale (autre module) définissant le nombre d'étapes |
|
FORM_SUBJECT |
Sujet du formulaire (mot clé utilisé dans le code) |
|
FORM_TITLE |
Titre du formulaire situé en haut de celui-ci |
XI. Les procédures événementielles des formulaires▲
XI-A. Le formulaire d'accueil (frmStart)▲
XI-A-1. L'en-tête de la feuille▲
L'en-tête ici comporte l'index du formulaire suivant et le titre de la fenêtre dans deux constantes dûment nommées.
Option Compare Database
Option Explicit
Private Const NEXT_FORM_ID As Integer = 1
Private Const FORM_CAPTION As String = "Écran d'accueil"XI-A-2. Les événements de formulaire▲
La procédure SurChargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'On désactive les alertes système
DoCmd.SetWarnings False
'On réinitialise les variables et les contrôles
Me.chkRAZData = False
g_strDataPage = ""
'On efface le tableau des étapes
Erase g_intCompletionIndex
Me.Caption = FORM_CAPTION
End SubOn désactive les alertes système.
La propriété Légende (titre de la fenêtre) voit sa valeur modifiée via la constante FORM_CAPTION.
Le titre (lblTitle) dessiné dans le formulaire est lui aussi alimenté dynamiquement via la constante FORM_TITLE.
On force la valeur False à la case à cocher et on attribue une valeur vide à g_strDataPage.
Quelques explications…
- Le contrôle case à cocher chkRAZData (Langue que vous reconnaissez grâce à son préfixe) est mis à False.
- Le tableau d'entiers nommé g_intCompletionIndex est vidé ; c'est lui qui contient la liste des étapes écran par écran et affecte une valeur True ou False à chacun des éléments selon que le formulaire est complété ou non.
Il n'y a pas d'autre événement pour ce formulaire.
XI-B. Le formulaire de saisie du site (frmDataWizard1)▲
XI-B-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Option Compare Database
Option Explicit
Private Const PREVIOUS_FORM_ID As Integer = 0
Private Const NEXT_FORM_ID As Integer = 2
Private Const TARGET_TABLE As String = "tblSites"
Private Const FIELD_COLLECTION As String = "NoSite;LibelleSite;IDCommune;DateOuverture"
Private Const RECORD_SOURCE As String = "SELECT * FROM " & TARGET_TABLE
Private Const DELETE_RECORD_SQL As String = "DELETE * FROM " & TARGET_TABLE & " "
Private Const WHERE_CONDITION As String = "WHERE NoSite = #1"
Private Const FORM_CAPTION As String = "Saisie des données : étape #1/" & MAX_STEP_WIZ
Private Const FORM_SUBJECT As String = "du Site"
Private Const FORM_TITLE As String = " Assistant de saisie des données " & FORM_SUBJECTVous reconnaîtrez parmi elles, les index des formulaires suivant et précédent, le titre et le sujet de la fenêtre. Les autres constantes concernent la navigation, la source de données et les différentes clauses SQL.
XI-B-2. Les événements de formulaire▲
La procédure SurChargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'Le formulaire est raccordé à la source de données de la constante
Me.RecordSource = RECORD_SOURCE
'On applique le titre de la barre de titre dynamiquement
Me.Caption = Replace(FORM_CAPTION, "#1", NEXT_FORM_ID - 1)
'On applique le titre de la fenêtre dynamiquement
Me.lblTitle.Caption = FORM_TITLE
'On appelle InitIDCommune()
Call InitIDCommune
End SubQuelques explications…
La propriété Légende (titre de la fenêtre) voit sa valeur modifiée via la constante FORM_CAPTION.
Le titre (lblTitle) dessiné dans le formulaire est lui aussi alimenté dynamiquement via la constante FORM_TITLE.
Le formulaire voit sa propriété RecordSource affectée à la constante du même nom RECORD_SOURCE.
La fonction InitIDCommune() remplit la zone de liste indépendante en fonction d'une commune qui aurait préalablement été renseignée (cas de navigation en allers et retours).
La fonction privée se trouve ici : InitIDCommune.
La procédure SurErreur()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Dim strErrMessage As String
'On appelle FormDataErrors() selon DataErr si une erreur est rencontrée
If FormDataErrors(DataErr, strErrMessage, Response, Me) Then
MsgBox strErrMessage, vbExclamation, "Erreur de saisie"
Me.ActiveControl.Undo
End If
End SubQuelques explications…
Cet événement appelle FormDataErrors() avec le paramètre DataErr si une erreur est rencontrée et affiche un message circonstanciel.
Il n'y a pas d'autre événement pour ce formulaire.
XI-C. Le formulaire de saisie du personnel (frmDataWizard2)▲
XI-C-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Option Compare Database
Option Explicit
Private Const PREVIOUS_FORM_ID As Integer = 1
Private Const NEXT_FORM_ID As Integer = 3
Private Const TARGET_TABLE As String = "tblPersonnel"
Private Const FIELD_COLLECTION As String = "NoSite;Prenom;NomFamille;Email;Login;IDPrivileges"
Private Const RECORD_SOURCE As String = "SELECT * FROM " & TARGET_TABLE & ";"
Private Const DELETE_RECORD_SQL As String = "DELETE * FROM " & TARGET_TABLE & " "
Private Const WHERE_CONDITION As String = "WHERE IDPersonne = #1 AND NoSite = '#2'"
Private Const FORM_CAPTION As String = "Saisie des données : étape #1/" & MAX_STEP_WIZ
Private Const FORM_SUBJECT As String = "des employés rattachés au Site"
Private Const FORM_TITLE As String = " Assistant de saisie des données " & FORM_SUBJECTXI-C-2. Les événements de formulaire▲
La procédure Sur_Activation()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Current()
'On appelle la procédure événementielle générique du même nom
Call FormCurrent(Me, "Prenom")
End SubQuelques explications…
On appelle la procédure générique FormCurrent avec le paramètre Me pour identifier le formulaire en cours et le nom du contrôle devant être sollicité dans celle-ci.
La procédure Sur_Erreur ()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Dim strErrMessage As String
'On appelle FormDataErrors() selon DataErr si une erreur est rencontrée
If FormDataErrors(DataErr, strErrMessage, Response, Me) Then
MsgBox strErrMessage, vbExclamation, "Erreur de saisie"
Me.ActiveControl.Undo
End If
End SubQuelques explications…
Cet événement appelle FormDataErrors() avec le paramètre DataErr si une erreur est rencontrée et affiche un message circonstanciel.
La procédure Sur_Chargement ()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'On désactive les alertes
DoCmd.SetWarnings False
'On masque ou affiche les contrôles selon le mode (Prod ou Test)
Me.txtTotalRows.Visible = SHOW_HIDDEN_CONTROLS
Me.txtNoSite.Visible = SHOW_HIDDEN_CONTROLS
Me.txtIDPersonne.Visible = SHOW_HIDDEN_CONTROLS
Me.txtNomPrenom.Visible = SHOW_HIDDEN_CONTROLS
Me.txtTotalRows.Visible = SHOW_HIDDEN_CONTROLS
Me.txtRowCompleted.Visible = SHOW_HIDDEN_CONTROLS
'Le formulaire est raccordé à la source de données de la constante
Me.RecordSource = RECORD_SOURCE
'On applique le titre de la barre de titre dynamiquement
Me.Caption = Replace(FORM_CAPTION, "#1", NEXT_FORM_ID - 1)
'On applique le titre de la fenêtre dynamiquement
Me.lblTitle.Caption = FORM_TITLE
End SubQuelques explications…
On affecte la visibilité des contrôles de sous-totaux via la constante SHOW_HIDDEN_CONTROLS.
La propriété Légende (titre de la fenêtre) voit sa valeur modifiée via la constante FORM_CAPTION.
Le titre (lblTitle) dessiné dans le formulaire est lui aussi alimenté dynamiquement via la constante FORM_TITLE.
Le formulaire voit sa propriété RecordSource affectée à la constante du même nom RECORD_SOURCE.
La procédure Sur_Minuterie()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Timer()
'On provoque un clignotement du texte de complétion des données
Me.lblRecCompleted.Visible = Not lblRecCompleted.Visible
End SubCet événement provoque ici le clignotement du texte de complétion des données selon la valeur du champ situé dans le sous-formulaire.
Il n'y a pas d'autre événement pour ce formulaire.
XI-D. Le sous-formulaire de saisie des communes (frmDataWizard2_sub)▲
XI-D-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Option Compare Database
Option Explicit
Private Const TARGET_TABLE As String = "tblPersonnel"
Private Const RECORD_SOURCE As String = "SELECT * FROM " & TARGET_TABLE & ";"XI-D-2. Les événements de formulaire▲
La procédure Sur_Activation()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Current()
'On appelle la procédure événementielle générique du même nom
Call SubFormCurrent(Me, "employé", False)
End SubQuelques explications…
On appelle la procédure générique FormCurrent avec le paramètre Me pour identifier le formulaire en cours et le nom du contrôle devant être sollicité dans celle-ci.
La procédure Sur_Erreur ()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Error(DataErr As Integer, Response As Integer)
'On appelle FormDataErrors() selon DataErr si une erreur est rencontrée
SubFormDataError Me, DataErr, Response, "même nom / même prénom / même N° de site"
End SubQuelques explications…
Cet événement appelle FormDataErrors() avec le paramètre DataErr si une erreur est rencontrée et affiche un message circonstanciel.
La procédure Sur_Chargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'On affecte la source de données
Me.RecordSource = RECORD_SOURCE
End SubQuelques explications…
Le formulaire voit sa propriété RecordSource affectée à la constante du même nom RECORD_SOURCE.
Il n'y a pas d'autre événement pour ce formulaire.
XI-E. Le formulaire de saisie des communes (frmDataWizard3)▲
XI-E-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Option Compare Database
Option Explicit
Private Const PREVIOUS_FORM_ID As Integer = 2
Private Const NEXT_FORM_ID As Integer = 4
Private Const TARGET_TABLE As String = "tblCommunesSite"
Private Const FIELD_COLLECTION As String = "IDCommune;NoSite"
Private Const RECORD_SOURCE As String = "SELECT * FROM " & TARGET_TABLE & ";"
Private Const DELETE_RECORD_SQL As String = "DELETE * FROM " & TARGET_TABLE & " "
Private Const WHERE_CONDITION As String = "WHERE IDCommune = #1 AND NoSite = #2"
Private Const FORM_CAPTION As String = "Saisie des données : étape #1/" & MAX_STEP_WIZ
Private Const FORM_SUBJECT As String = "des communes du site"
Private Const FORM_TITLE As String = " Assistant de saisie des données " & FORM_SUBJECTXI-E-2. Les événements de formulaire▲
La procédure Sur_Activation()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Current()
'On appelle la procédure événementielle générique du même nom
Call FormCurrent(Me, "IDDepartement")
End SubQuelques explications…
On appelle la procédure générique FormCurrent avec le paramètre Me pour identifier le formulaire en cours et le nom du contrôle devant être sollicité dans celle-ci.
La procédure Sur_Erreur ()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Dim strErrMessage As String
'On appelle FormDataErrors() selon DataErr si une erreur est rencontrée
If FormDataErrors(DataErr, strErrMessage, Response, Me) Then
MsgBox strErrMessage, vbExclamation, "Erreur de saisie"
Me.ActiveControl.Undo
End If
End SubQuelques explications…
Cet événement appelle FormDataErrors() avec le paramètre DataErr si une erreur est rencontrée et affiche un message circonstanciel.
La procédure Sur_Chargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'On désactive les alertes
DoCmd.SetWarnings False
'On masque ou affiche les contrôles selon le mode (Prod ou Test)
Me.txtTotalRows.Visible = SHOW_HIDDEN_CONTROLS
Me.txtIDCommune.Visible = SHOW_HIDDEN_CONTROLS
Me.txtCommune.Visible = SHOW_HIDDEN_CONTROLS
Me.txtRowCompleted.Visible = SHOW_HIDDEN_CONTROLS
'Le formulaire est raccordé à la source de données de la constante
Me.RecordSource = RECORD_SOURCE
'On applique le titre de la barre de titre dynamiquement
Me.Caption = Replace(FORM_CAPTION, "#1", NEXT_FORM_ID - 1)
'On applique le titre de la fenêtre dynamiquement
Me.lblTitle.Caption = FORM_TITLE
End SubQuelques explications…
On affecte la visibilité des contrôles de sous-totaux via la constante SHOW_HIDDEN_CONTROLS.
La propriété Légende (titre de la fenêtre) voit sa valeur modifiée via la constante FORM_CAPTION.
Le titre (lblTitle) dessiné dans le formulaire est lui aussi alimenté dynamiquement via la constante FORM_TITLE.
Le formulaire voit sa propriété RecordSource affectée à la constante du même nom RECORD_SOURCE.
La procédure Sur_Minuterie()
Private Sub Form_Timer()
Dim lngCount As Long
'On provoque un clignotement du texte de complétion des données
Me.lblRecCompleted.Visible = Not lblRecCompleted.Visible
End SubQuelques explications…
Cet événement provoque ici le clignotement du texte de complétion des données selon la valeur du champ situé dans le sous-formulaire.
Il n'y a pas d'autre événement pour ce formulaire.
XI-F. Le sous-formulaire de saisie des communes (frmDataWizard3_sub)▲
XI-F-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Option Compare Database
Option Explicit
Private Const TARGET_TABLE As String = "tblCommunesSite"
Private Const RECORD_SOURCE As String = "SELECT * FROM " & TARGET_TABLE & ";"XI-F-2. Les événements▲
La procédure Sur_Activation()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Current()
'On appelle la procédure événementielle générique du même nom
Call SubFormCurrent(Me, "cboDepartement", False)
End SubQuelques explications…
On appelle la procédure générique FormCurrent avec le paramètre Me pour identifier le formulaire en cours et le nom du contrôle devant être sollicité dans celle-ci.
La procédure Sur_Erreur ()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Error(DataErr As Integer, Response As Integer)
'On appelle FormDataErrors() selon DataErr si une erreur est rencontrée
SubFormDataError Me, DataErr, Response, "même département / même commune / même N° de site"
End SubQuelques explications…
Cet événement appelle SubFormDataErrors() avec le paramètre DataErr si une erreur est rencontrée et affiche un message circonstanciel.
La procédure Sur_Chargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'On affecte la source de données
Me.RecordSource = RECORD_SOURCE
End SubQuelques explications…
Le formulaire voit sa propriété RecordSource affectée à la constante du même nom RECORD_SOURCE.
Il n'y a pas d'autre événement pour ce formulaire.
XI-G. Le formulaire de saisie des contrats (frmDataWizard4)▲
XI-G-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Option Compare Database
Option Explicit
Private Const PREVIOUS_FORM_ID As Integer = 3
Private Const NEXT_FORM_ID As Integer = 5
Private Const TARGET_TABLE As String = "tblContrats"
Private Const FIELD_COLLECTION As String = "IDContrat;LibelleContrat;IDTypeContrat;DateDebut;DateFin"
Private Const RECORD_SOURCE As String = "SELECT * FROM " & TARGET_TABLE
Private Const FORM_CAPTION As String = "Saisie des données : étape #1/" & MAX_STEP_WIZ
Private Const FORM_SUBJECT As String = "des Contrats"
Private Const FORM_TITLE As String = " Assistant de saisie des données " & FORM_SUBJECTXI-G-2. Les événements de formulaire▲
La procédure Sur_Erreur ()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Dim strErrMessage As String
'On appelle FormDataErrors() selon DataErr si une erreur est rencontrée
If FormDataErrors(DataErr, strErrMessage, Response, Me) Then
MsgBox strErrMessage, vbExclamation, "Erreur de saisie"
Me.ActiveControl.Undo
End If
End SubQuelques explications…
Cet événement appelle FormDataErrors() avec le paramètre DataErr si une erreur est rencontrée et affiche un message circonstanciel.
La procédure Sur_Chargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
Dim oRS As DAO.Recordset
Dim lngRecordCount As Long
'Le formulaire est raccordé à la source de données de la constante
Me.RecordSource = RECORD_SOURCE
'On applique le titre de la barre de titre dynamiquement
Me.Caption = Replace(FORM_CAPTION, "#1", NEXT_FORM_ID - 1)
'On applique le titre de la fenêtre dynamiquement
Me.lblTitle.Caption = FORM_TITLE
'On compte le nombre de contrats afin de libérer le bouton donnant accès à la liste
Set oRS = Me.RecordsetClone
With oRS
If Not .EOF Then
.MoveLast
lngRecordCount = .RecordCount
.MoveFirst
End If
.Close
End With
Me.cmdShowExistingContracts.Enabled = (lngRecordCount > 1)
Set oRS = Nothing
End SubQuelques explications…
Le formulaire voit sa propriété RecordSource affectée à la constante du même nom RECORD_SOURCE.
La propriété Légende (titre de la fenêtre) voit sa valeur modifiée via la constante FORM_CAPTION.
Le titre (lblTitle) dessiné dans le formulaire est lui aussi alimenté dynamiquement via la constante FORM_TITLE.
On compte alors le nombre d'enregistrements que l'on affecte à la variable lngRecordCount dont la valeur détermine l'activation du bouton cmdShowExistingContracts permettant d'afficher un popup si elle supérieure à 1.
Il n'y a pas d'autre événement pour ce formulaire.
XI-H. Le formulaire listant les contrats (frmDataWizard4_Popup)▲
XI-H-1. L'en-tête de la feuille▲
L'en-tête ici ne comporte aucune déclaration.
Option Compare Database
Option ExplicitXI-H-2. Les événements▲
La procédure Sur_Chargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
Me.txtFocus.SetFocus
End SubQuelques explications…
On donne le focus au contrôle txtFocus dissimulé, mais pas invisible (enfin, il faut de bons yeux tout de même).
Il est situé en haut et à gauche avec des dimensions et positions, toutes définies à zéro.
Il n'y a pas d'autre événement pour ce formulaire.
XI-I. Le formulaire de saisie de l'association Contrat-Communes (frmDataWizard5)▲
XI-I-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Option Compare Database
Option Explicit
Private Const PREVIOUS_FORM_ID As Integer = 4
Private Const NEXT_FORM_ID As Integer = MAX_STEP_WIZ + 1
Private Const FINAL_FORM_ID As Integer = LAST_FORM_INDEX
Private Const TARGET_TABLE As String = "tblContratCommunes"
Private Const FIELD_COLLECTION As String = "IDCommune;IDContrat"
Private Const RECORD_SOURCE As String = "SELECT * FROM " & TARGET_TABLE & ";"
Private Const DELETE_RECORD_SQL As String = "DELETE * FROM " & TARGET_TABLE & " "
Private Const WHERE_CONDITION As String = "WHERE IDCommune = #1 AND IDContrat = '#2'"
Private Const FORM_CAPTION As String = "Saisie des données : étape #1/" & MAX_STEP_WIZ
Private Const FORM_SUBJECT As String = "des contrats des communes du site"
Private Const FORM_TITLE As String = " Assistant de saisie des données " & FORM_SUBJECTXI-I-2. Les événements de formulaire▲
La procédure Sur_Erreur ()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Dim strErrMessage As String
'On appelle FormDataErrors() selon DataErr si une erreur est rencontrée
If FormDataErrors(DataErr, strErrMessage, Response, Me) Then
MsgBox strErrMessage, vbExclamation, "Erreur de saisie"
Me.ActiveControl.Undo
End If
End SubQuelques explications…
Cet événement appelle FormDataErrors() avec le paramètre DataErr si une erreur est rencontrée et affiche un message circonstanciel.
La procédure Sur_Chargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'On désactive les alertes
DoCmd.SetWarnings False
'On masque ou affiche les contrôles selon le mode (Prod ou Test)
Me.txtTotalRows.Visible = SHOW_HIDDEN_CONTROLS
'Me.txtIDCommune.Visible = SHOW_HIDDEN_CONTROLS
'Me.txtCommune.Visible = SHOW_HIDDEN_CONTROLS
Me.txtRowCompleted.Visible = SHOW_HIDDEN_CONTROLS
'Le formulaire est raccordé à la source de données de la constante
Me.RecordSource = RECORD_SOURCE
'On applique le titre de la barre de titre dynamiquement
Me.Caption = Replace(FORM_CAPTION, "#1", NEXT_FORM_ID - 1)
'On applique le titre de la fenêtre dynamiquement
Me.lblTitle.Caption = FORM_TITLE
End SubQuelques explications…
On affecte la visibilité des contrôles de sous-totaux via la constante SHOW_HIDDEN_CONTROLS.
La propriété Légende (titre de la fenêtre) voit sa valeur modifiée via la constante FORM_CAPTION.
Le titre (lblTitle) dessiné dans le formulaire est lui aussi alimenté dynamiquement via la constante FORM_TITLE.
Le formulaire voit sa propriété RecordSource affectée à la constante du même nom RECORD_SOURCE.
La procédure Sur_Minuterie()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Timer()
'On provoque un clignotement du texte de complétion des données
Me.lblRecCompleted.Visible = Not lblRecCompleted.Visible
End SubQuelques explications…
Cet événement provoque ici le clignotement du texte de complétion des données selon la valeur du champ situé dans le sous-formulaire.
XI-J. Le sous-formulaire de saisie des communes (frmDataWizard5_sub)▲
XI-J-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Private Const TARGET_TABLE As String = "tblContratCommunes"
Private Const RECORD_SOURCE As String = "SELECT * FROM " & TARGET_TABLE & ";"XI-J-2. Les événements▲
La procédure Sur_Activation()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Current()
'On appelle la procédure événementielle générique du même nom
Call SubFormCurrent(Me, "Contrat / Communes", False)
End SubQuelques explications…
On appelle la procédure générique FormCurrent avec le paramètre Me pour identifier le formulaire en cours et le nom du contrôle devant être sollicité dans celle-ci.
La procédure Sur_Erreur ()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Error(DataErr As Integer, Response As Integer)
'On appelle FormDataErrors() selon DataErr si une erreur est rencontrée
SubFormDataError Me, DataErr, Response, "même département / même commune / même N° de site"
End SubQuelques explications…
Cet événement appelle SubFormDataErrors() avec le paramètre DataErr si une erreur est rencontrée et affiche un message circonstanciel.
La procédure Sur_Chargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'On affecte la source de données
Me.RecordSource = RECORD_SOURCE
End SubQuelques explications…
Le formulaire voit sa propriété RecordSource affectée à la constante du même nom RECORD_SOURCE.
Il n'y a pas d'autre événement pour ce formulaire.
XI-K. Le formulaire de fin (frmEnd)▲
XI-K-1. L'en-tête de la feuille▲
L'en-tête ici comporte le lot de constantes définies dans ce tableau.
Private Const PREVIOUS_FORM_ID As Integer = 5
Private Const NEXT_FORM_ID As Integer = 0
Private Const FORM_CAPTION As String = "Saisie des données : dernière étape"
Private Const FORM_SUBJECT As String = "pour l'ensemble des modules"
Private Const FORM_TITLE As String = "Fin de l'Assistant de saisie des données " & FORM_SUBJECT
Private m_blnCompleted As Boolean
Private m_intCount As IntegerQuelques explications…
La variable m_blnCompleted est utilisée pour déterminer la complétude des étapes et la variable m_intCount pour les compter.
XI-K-2. Les événements▲
La procédure Sur_Chargement()
Elle est invoquée et se traduit par le code suivant :
Private Sub Form_Load()
'On applique le titre de la barre de titre dynamiquement
Me.Caption = FORM_CAPTION
'On applique le titre de la fenêtre dynamiquement
Me.lblTitle.Caption = FORM_TITLE
'On initialise la variable selon la résultante de la fonction
m_blnCompleted = AllStepsAreCompleted(m_intCount)
'On affiche la liste des étapes manquantes s'il y en a
If Not m_blnCompleted Then
Me.lblStepList.Caption = g_strDataPage
lblStepStatus.Visible = True
Else
Me.lblStepList.Caption = "Aucune étape n'a été omise."
lblStepStatus.Visible = False
End If
End SubQuelques explications…
La propriété Légende (titre de la fenêtre) voit sa valeur modifiée via la constante FORM_CAPTION.
Le titre (lblTitle) dessiné dans le formulaire est lui aussi alimenté dynamiquement via la constante FORM_TITLE.
On affecte à la variable m_blnCompleted le fait que toutes les étapes sont complétées et on change l'intitulé des étiquettes en conséquence.
XII. Les procédures événementielles des contrôles▲
XII-A. Le formulaire d'accueil (frmStart)▲
XII-A-1. L'en-tête de la feuille▲
L'en-tête ici comporte l'index du formulaire suivant et le titre de la fenêtre dans deux constantes dûment nommées.
Option Compare Database
Option Explicit
Private Const NEXT_FORM_ID As Integer = 1
Private Const FORM_CAPTION As String = "Écran d'accueil"XII-A-2. Les événements▲
La procédure événementielle des boutons est systématiquement appelée via l'événement SurClic() ; ces procédures se traduisent par les blocs de code suivants :
Le bouton Quitter
Private Sub cmdQuit_Click()
Dim strMsgData As String
Dim intCount As Integer
'Appelle la fonction pour déterminer si toutes les étapes sont complétées
If AllStepsAreCompleted(intCount) Then
'La chaîne prend alors le message correspondant (ici positif)
strMsgData = vbCrLf & vbCrLf & "Tous les modules sont complétés"
Else
'La chaîne prend alors le message correspondant (ici négatif)
strMsgData = vbCrLf & vbCrLf & "Toutes les données n'ont pas été saisies." & vbCrLf & vbCrLf & IIf(intCount = 0, "Aucune étape n'a été correctement remplie !", Trim(str(intCount)) & " étape(s) a été correctement remplie(s) sur un total de " & MAX_STEP_WIZ & ".")
End If
'On demande confirmation avant de quitter l'application
If MsgBox("Êtes-vous certain de vouloir quitter ?" & strMsgData, vbQuestion + vbYesNo + vbDefaultButton2, "Quitter") = vbYes Then
Application.Quit acQuitSaveAll
End If
End SubQuelques explications…
On appelle la fonction AllStepsAreCompleted() pour prévenir (toujours de façon optimiste) la complétude des données, avant de quitter l'application, par un message à l'utilisateur.
Le bouton Démarrer
Private Sub cmdStart_Click()
Dim strErrDescription As String
On Error GoTo L_ErrcmdStart_Click
'Si la remise à blanc des données est demandée...
If Nz(Me.chkRAZData, False) Then
'On confirme par un message la remise à blanc avant de passer la première étape
Select Case MsgBox("Êtes-vous certain de vouloir supprimer toutes les données déjà inscrites avant de continuer ?" & vbCrLf & _
"Cette action est irréversible...", vbExclamation + vbYesNoCancel + vbDefaultButton2, "Effacer toutes les données")
Case vbYes
'On appelle la fonction qui retourne un succès ou un échec
If EraseAllDataBefore(strErrDescription) = False Then
'On génère l'erreur d'exécution si la fonction échoue
Err.Raise 17, "Remise à blanc", strErrDescription
End If
Case vbCancel
'On ne fait rien
Exit Sub
Case Else
End Select
End If
'On ferme cet écran
DoCmd.Close acForm, Me.Name
'On ouvre l'écran de l'étape 1
DoCmd.OpenForm FORM_DATA_WIZ & NEXT_FORM_ID, , , , , acDialog
On Error GoTo 0
L_ExcmdStart_Click:
Exit Sub
L_ErrcmdStart_Click:
MsgBox Err.Description, vbExclamation, Err.Source
Resume L_ExcmdStart_Click
End SubQuelques explications…
Si la case de remise à blanc est cochée, alors :
- on confirme par un message cette remise à blanc avant de passer la première étape ;
- on appelle alors la fonction EraseAllDataBefore qui retourne un succès ou un échec ;
- on ferme cet écran ;
- on ouvre l'écran de l'étape 1.
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XII-B. Le formulaire de saisie du site (frmDataWizard1)▲
Le contrôle cboDeptRegion
Private Sub cboDeptRegion_Click()
Dim SQLListeCommunes As String
Dim strNoDepartement As String
'On construit la clause SQL selon le département sélectionné
strNoDepartement = Me.cboDeptRegion
SQLListeCommunes = "SELECT IDCommune, [NomCommune] & ' (' & [CodePostal] & ')' AS Commune, IDDepartement "
SQLListeCommunes = SQLListeCommunes & "FROM tbl_ListeDesCommunes WHERE IDDepartement='" & strNoDepartement & "';"
'On affecte la requête à la liste des communes, filtrée sur le département sélectionné
With Me.IDCommune
.RowSource = SQLListeCommunes
'On active la liste, lui donne le focus et lui force le déroulement
.Enabled = True
.SetFocus
.Dropdown
End With
End SubQuelques explications…
Le contrôle IDCommune se voit affecter la requête source SQL en fonction du numéro du département choisi.
Le bouton Supprimer
Private Sub cmdDelete_Click()
Dim SQLDelete As String
Dim strSubject As String
'S'il n'y a pas de nullité
If Not IsNull(Me.NoSite) And Not IsNull(Me.LibelleSite) Then
strSubject = "le site " & Me.LibelleSite & " (" & Me.NoSite & ") "
SQLDelete = DELETE_RECORD_SQL & Replace(WHERE_CONDITION, "#1", Me.NoSite)
'On appelle la méthode DeleteOrSaveRecord avec les paramètres attendus
DeleteOrSaveRecord Me, 3, Me.NoSite, SQLDelete, strSubject
End If
End SubQuelques explications…
Si l'identifiant cible et le libellé ne sont pas nuls alors :
- on alimente la chaîne strSubject du contexte qui fera office de confirmation du message de suppression ;
- on élabore la clause SQL de suppression dans la variable SQLDelete avec les constantes de l'en-tête dûment remplacées par la fonction Replace() ;
- on appelle la procédure DeleteOrSaveRecord () avec les paramètres attendus.
Le bouton Suivant
Private Sub cmdNext_Click()
'On affecte l'identifiant du site à la variable globale (elle est exploitée pour tout l'assistant)
g_strNoSiteEnCours = Nz(Me.NoSite, "")
'On suppose (c'est un exemple) que le nom du site est le nom du domaine
g_strDomaineSiteEnCours = Replace(LCase(Nz(Me.LibelleSite, "")), " ", "") & ".com"
'On appelle la procédure pour ouvrir le formulaire suivant
OpenTheForm GotoNext, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, False
End SubQuelques explications…
On accède au formulaire suivant à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Le bouton Précédent
Private Sub cmdPrevious_Click()
'On appelle la procédure pour ouvrir le formulaire précédent
OpenTheForm GotoPrevious, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, False
End SubQuelques explications…
On accède au formulaire précédent à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Le contrôle LibelleSite
Private Sub LibelleSite_AfterUpdate()
'On force la casse en majuscules
Me.LibelleSite = UCase(Me.LibelleSite)
Me.cmdDelete.Enabled = (Nz(Me.NoSite, 0) And Len(Nz(Me.LibelleSite, "")))
End SubQuelques explications…
On force la casse du libellé du site en majuscules et on active le bouton de suppression en fonction de la valeur de deux champs.
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XII-C. Le formulaire de saisie du personnel (frmDataWizard2)▲
Le bouton Ajouter
Private Sub cmdAdd_Click()
'On appelle la méthode AddNewOrCancel avec les paramètres attendus
AddNewOrCancel Me, NEXT_FORM_ID - 1, TARGET_TABLE, FIELD_COLLECTION, "Prenom", True, NEXT_FORM_ID
End SubQuelques explications…
On appelle ici la procédure AddNewOrCancel () vue et détaillée ci-après pour ajouter ou annuler un enregistrement.
Le bouton Supprimer
Private Sub cmdDelete_Click()
Dim SQLDelete As String
Dim strSubject As String
If Not IsNull(Me.txtIDPersonne) Then
strSubject = "l'employé " & IIf(Len(Nz(Me.txtNomPrenom, "")), " (" & txtNomPrenom & ") ", "non spécifié")
SQLDelete = DELETE_RECORD_SQL & Replace(Replace(WHERE_CONDITION, "#1", Me.txtIDPersonne), "#2", g_strNoSiteEnCours)
'On appelle la méthode DeleteOrSaveRecord avec les paramètres attendus
DeleteOrSaveRecord Me, 3, Me.txtIDPersonne, SQLDelete, strSubject
End If
End SubQuelques explications…
Si l'identifiant cible n'est pas nul alors :
- on alimente la chaîne strSubject du contexte qui fera office de confirmation du message de suppression ;
- on élabore la clause SQL de suppression dans la variable SQLDelete avec les constantes de l'en-tête dûment remplacées par la fonction Replace() ;
- on appelle la procédure DeleteOrSaveRecord () avec les paramètres attendus.
Le bouton Suivant
Private Sub cmdNext_Click()
'On appelle la procédure pour ouvrir le formulaire suivant
OpenTheForm GotoNext, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, True
End SubQuelques explications…
On accède au formulaire suivant à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Le bouton Précédent
Private Sub cmdPrevious_Click()
'On appelle la procédure pour ouvrir le formulaire précédent
OpenTheForm GotoPrevious, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, True
End SubQuelques explications…
On accède au formulaire précédent à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
XII-D. Le sous-formulaire de saisie des communes (frmDataWizard2_sub)▲
Le contrôle chkCompleted
Private Sub chkCompleted_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
'On donne le focus : la case à cocher ne doit pas être cliquée
Me.Prenom.SetFocus
End SubQuelques explications…
Si l'utilisateur tente de cliquer sur la case à cocher, on donne le focus au premier contrôle.
Le contrôle Email
Private Sub EMAIL_AfterUpdate()
Me.EMAIL = LCase(Me.EMAIL)
Me.NoSite = g_strNoSiteEnCours
End SubQuelques explications…
On force la casse de l'adresse mail en minuscules et le champ NoSite prend la valeur de la variable globale représentant le numéro du site, initialisée dans le premier formulaire de saisie.
Le contrôle IDPrivileges
Private Sub IDPrivileges_AfterUpdate()
Me.NoSite = g_strNoSiteEnCours
End SubQuelques explications…
Le champ NoSite prend la valeur de la variable globale représentant le numéro du site.
Le contrôle IDPrivileges
Private Sub IDPrivileges_NotInList(NewData As String, Response As Integer)
'On appelle la méthode NotInList pour avertir de la non-correspondance
Response = NotInList(NewData, "Le droit attribué")
End SubQuelques explications…
On appelle ici la fonction NotInList() vue et détaillée ci-après…
Le contrôle Login
Private Sub Login_AfterUpdate()
Me.NoSite = g_strNoSiteEnCours
End SubQuelques explications…
Le champ NoSite prend la valeur de la variable globale représentant le numéro du site.
Le contrôle NomFamille
Private Sub NomFamille_AfterUpdate()
'On force en MAJUSCULES
Me.NomFamille = UCase(Me.NomFamille)
'On force le champ avec la valeur commune de l'identifiant
Me.NoSite = g_strNoSiteEnCours
'On construit l'adresse mail si on peut
Me.EMAIL = LCase(Nz(Me.Prenom, "???") & "." & Me.NomFamille & "@" & g_strDomaineSiteEnCours)
'Le login est par défaut le Nom.P
Me.Login = UCase(Me.NomFamille) & "." & Left(Nz(Me.Prenom, " "), 1)
End SubQuelques explications…
On force la casse du nom de famille en majuscules et on finit de composer l'adresse mail fondée sur la règle supposée Email = .
Le contrôle Prenom
Private Sub Prenom_AfterUpdate()
'On force en Nom Propre
Me.Prenom = StrConv(Me.Prenom, vbProperCase)
'On force le champ avec la valeur commune de l'identifiant
Me.NoSite = g_strNoSiteEnCours
'On construit l'adresse mail si on peut
Me.EMAIL = LCase(Me.Prenom & "." & Nz(Me.NomFamille, "???") & "@" & g_strDomaineSiteEnCours)
End SubQuelques explications…
On force la casse du prénom en nom propre et on finit de composer l'adresse mail fondée sur la règle supposée Email = .
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XII-E. Le formulaire de saisie des communes (frmDataWizard3)▲
Le bouton Ajouter
Private Sub cmdAdd_Click()
'On appelle la méthode AddNewOrCancel avec les paramètres attendus
AddNewOrCancel Me, NEXT_FORM_ID - 1, TARGET_TABLE, FIELD_COLLECTION, "cboDepartement", True, NEXT_FORM_ID
End SubQuelques explications…
On appelle ici la procédure AddNewOrCancel () vue et détaillée ci-après pour ajouter ou annuler un enregistrement.
Le bouton Supprimer
Private Sub cmdDelete_Click()
Dim SQLDelete As String
Dim strSubject As String
If Not IsNull(Me.txtIDCommune) Then
strSubject = "la commune " & Me.txtCommune
SQLDelete = DELETE_RECORD_SQL & Replace(Replace(WHERE_CONDITION, "#1", Me.txtIDCommune), "#2", g_strNoSiteEnCours)
'On appelle la méthode DeleteOrSaveRecord avec les paramètres attendus
DeleteOrSaveRecord Me, 3, Me.txtIDCommune, SQLDelete, strSubject
End If
End SubQuelques explications…
Si l'identifiant cible n'est pas nul alors :
- on alimente la chaîne strSubject du contexte qui fera office de confirmation du message de suppression ;
- on élabore la clause SQL de suppression dans la variable SQLDelete avec les constantes de l'en-tête dûment remplacées par la fonction Replace() ;
- on appelle la procédure DeleteOrSaveRecord () avec les paramètres attendus.
Le bouton Suivant
Private Sub cmdNext_Click()
'On appelle la procédure pour ouvrir le formulaire suivant
OpenTheForm GotoNext, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, True
End SubQuelques explications…
On accède au formulaire suivant à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Le bouton Précédent
Private Sub cmdPrevious_Click()
'On appelle la procédure pour ouvrir le formulaire précédent
OpenTheForm GotoPrevious, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, True
End SubQuelques explications…
On accède au formulaire précédent à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XII-F. Le sous-formulaire de saisie des communes (frmDataWizard3_sub)▲
Le contrôle cboDepartement (1)
Private Sub cboDepartement_AfterUpdate()
Const TOWN_LIST As String = "SELECT C.IDCommune, [NomCommune] & ' (' & [CodePostal] & ')' AS Commune, C.CodePostal FROM tbl_ListeDepartements AS D INNER JOIN tbl_ListeDesCommunes AS C ON D.IDDepartement = C.IDDepartement WHERE (D.IDDepartement = '#') ORDER BY C.CodePostal;"
Dim strRowSource As String
'On construit la clause SQL selon le département sélectionné
strRowSource = Replace(TOWN_LIST, "#", Nz(Me.cboDepartement.Column(0), ""))
'On affecte la requête à la liste des communes, filtrée sur le département sélectionné
With Me.IDCommune
.RowSource = strRowSource
'On lui donne le focus et lui force le déroulement
.SetFocus
.Dropdown
End With
End SubQuelques explications…
Le contrôle IDCommune se voit affecter la requête source SQL en fonction du numéro du département choisi.
Le contrôle cboDepartement (2)
Private Sub cboDepartement_NotInList(NewData As String, Response As Integer)
'On appelle la méthode NotInList pour avertir de la non-correspondance
Response = NotInList(NewData, "Le département")
End SubQuelques explications…
On appelle ici la fonction NotInList() vue et détaillée ci-après…
Le contrôle chkCompleted
Private Sub chkCompleted_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
'On donne le focus : la case à cocher ne doit pas être cliquée
Me.cboDepartement.SetFocus
End SubQuelques explications…
Si l'utilisateur tente de cliquer sur la case à cocher, on donne le focus au premier contrôle.
Le contrôle IDCommune (1)
Private Sub IDCommune_AfterUpdate()
Me.NoSite = g_strNoSiteEnCours
End SubQuelques explications…
Le champ NoSite prend la valeur de la variable globale représentant le numéro du site.
Le contrôle IDCommune (2)
Private Sub IDCommune_NotInList(NewData As String, Response As Integer)
'On appelle la méthode NotInList pour avertir de la non-correspondance
Response = NotInList(NewData, "La commune")
End SubQuelques explications…
On appelle ici la fonction NotInList() vue et détaillée ci-après…
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XII-G. Le formulaire de saisie des contrats (frmDataWizard4)▲
Le contrôle LibelleContrat
Private Sub LibelleContrat_AfterUpdate()
'On force la casse en majuscules
Me.LibelleContrat = UCase(Me.ibelleContrat)
End SubQuelques explications…
La zone de texte se voit attribuer sa valeur en MAJUSCULES.
Le bouton cmdShowExistingContracts
Private Sub cmdShowExistingContracts_Click()
DoCmd.OpenForm Me.Name & "_Popup", , , , , acDialog
End SubQuelques explications…
On ouvre le formulaire via l'objet DoCmd et sa méthode OpenForm.
Remarque
Le fait de nommer correctement les objets permet de faciliter la rédaction du code sans galérer à chercher comment tel ou tel objet se nomme et en pouvant en plus, user ici du nom du formulaire parent…
Le bouton Suivant
Private Sub cmdNext_Click()
'On appelle la procédure pour ouvrir le formulaire suivant
OpenTheForm GotoNext, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, False
End SubQuelques explications…
On accède au formulaire suivant à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Le bouton Précédent
Private Sub cmdPrevious_Click()
'On appelle la procédure pour ouvrir le formulaire précédent
OpenTheForm GotoPrevious, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, False
End SubQuelques explications…
On accède au formulaire précédent à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
XII-H. Le formulaire listant les contrats (frmDataWizard4_Popup)▲
Le bouton Fermer
Private Sub cmdClose_Click()
DoCmd.Close acForm, Me.Name
End SubQuelques explications…
Ici, on ferme simplement le formulaire.
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XII-I. Le formulaire de saisie de l'association Contrat-Communes (frmDataWizard5)▲
Le bouton Ajouter
Private Sub cmdAdd_Click()
'On appelle la méthode AddNewOrCancel avec les paramètres attendus
AddNewOrCancel Me, NEXT_FORM_ID - 1, TARGET_TABLE, FIELD_COLLECTION, "cboDepartement", True, NEXT_FORM_ID
End SubQuelques explications…
On appelle ici la procédure AddNewOrCancel () vue et détaillée ci-après pour ajouter ou annuler un enregistrement.
Le bouton Supprimer
Private Sub cmdDelete_Click()
Dim SQLDelete As String
Dim strSubject As String
If Not IsNull(Me.txtIDCommune) Then
strSubject = "Le contrat " & Me.txtIDContrat & "de la commune " & Me.txtNomCommune
SQLDelete = DELETE_RECORD_SQL & Replace(Replace(WHERE_CONDITION, "#1", Me.txtIDCommune), "#2", Me.txtIDContrat)
'On appelle la méthode DeleteOrSaveRecord avec les paramètres attendus
DeleteOrSaveRecord Me, 3, Me.txtIDCommune, SQLDelete, strSubject
End If
End SubQuelques explications…
Si l'identifiant cible n'est pas nul alors :
- on alimente la chaîne strSubject du contexte qui fera office de confirmation du message de suppression ;
- on élabore la clause SQL de suppression dans la variable SQLDelete avec les constantes de l'en-tête dûment remplacées par la fonction Replace() ;
- on appelle la procédure DeleteOrSaveRecord () avec les paramètres attendus.
Le bouton Suivant
Private Sub cmdNext_Click()
'On appelle la procédure pour ouvrir le formulaire suivant
OpenTheForm GotoNext, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, True, False
End SubQuelques explications…
On accède au formulaire suivant à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Le bouton Précédent
Private Sub cmdPrevious_Click()
'On appelle la procédure pour ouvrir le formulaire précédent
OpenTheForm GotoPrevious, Me, NEXT_FORM_ID, PREVIOUS_FORM_ID, TARGET_TABLE, FIELD_COLLECTION, True, False
End SubQuelques explications…
On accède au formulaire précédent à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XII-J. Le sous-formulaire de saisie des communes (frmDataWizard5_sub)▲
Le contrôle chkCompleted
Private Sub chkCompleted_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
Me.IDContrat.SetFocus
End SubQuelques explications…
Si l'utilisateur tente de cliquer sur la case à cocher, on donne le focus au premier contrôle.
Le contrôle IDCommune
Private Sub IDCommune_NotInList(NewData As String, Response As Integer)
'On appelle la méthode NotInList pour avertir de la non-correspondance
Response = NotInList(NewData, "La commune")
End SubQuelques explications…
On appelle ici la fonction NotInList() vue et détaillée ci-après…
Le contrôle IDContrat
Private Sub IDContrat_NotInList(NewData As String, Response As Integer)
'On appelle la méthode NotInList pour avertir de la non-correspondance
Response = NotInList(NewData, "Le contrat")
End SubQuelques explications…
On appelle ici la fonction NotInList() vue et détaillée ci-après…
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XII-K. Le formulaire de fin (frmEnd)▲
Le bouton Début
Private Sub cmdFirst_Click()
'On accède au premier écran
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm FORM_START, , , , , acDialog
End SubQuelques explications…
Très simplement, ici on ferme ce formulaire et on ouvre le premier, c'est-à-dire celui de l'accueil défini par la constante FORM_START.
Le bouton Précédent
Private Sub cmdPrevious_Click()
'On bascule sur l'écran précédent selon l'index
If PREVIOUS_FORM_ID Then
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm FORM_DATA_WIZ & PREVIOUS_FORM_ID, , , , , acDialog
Else
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm FORM_START, , , , , acDialog
End If
End SubQuelques explications…
S'il existe une constante PREVIOUS_FORM_ID avec une valeur différente de zéro alors on considère qu'il y a un formulaire à afficher autre que celui de l'accueil ; sinon, on accède à ce dernier.
On accède alors au formulaire ciblé à l'aide de la fonction OpenTheForm () vue et détaillée ci-après…
Le bouton Terminer
Private Sub cmdEnd_Click()
Dim strMsgData As String
'On vérifie que tout est rempli par étape :
'On initialise le message en conséquence
If m_blnCompleted Then
strMsgData = "Tous les modules sont complétés" & vbCrLf & vbCrLf & "Voulez-vous quitter l'application ?"
Else
strMsgData = "Toutes les données n'ont pas été saisies." & vbCrLf & vbCrLf & IIf(m_intCount = 0, "Aucune étape n'a été correctement remplie !", Trim(str(m_intCount)) & " étape(s) a été correctement remplie(s) sur un total de " & MAX_STEP_WIZ & ".") & vbCrLf & vbCrLf & "Êtes-vous certain de vouloir quitter ?"
End If
'On affiche alors le message
If MsgBox(strMsgData, 292, "Quitter") = 6 Then
'On quitte
Application.Quit acQuitSaveAll
End If
End SubQuelques explications…
Selon l'état de m_blnCompleted, le message de la variable strMsgData change.
Un MsgBox est affiché pour confirmer la fermeture de l'application.
Il n'y a pas d'autre événement sur les contrôles pour ce formulaire.
XIII. Les fonctions et procédures locales au sein des différents formulaires▲
Seul le formulaire de saisie du Site possède une procédure privée locale.
XIII-A. Le formulaire de saisie du site (frmDataWizard1)▲
SIGNET InitIDCommune
XIII-A-1. La procédure locale InitIDCommune▲
Private Sub InitIDCommune()
Dim strIDDepartement As String
'Vérifie si une commune a déjà été spécifiée et agit en conséquence sur
'la zone modifiable (qui est indépendante) qui contient les départements
With Me.IDCommune
If Nz(.Value, 0) Then
strIDDepartement = .Column(2)
.Enabled = True
End If
End With
Me.cboDeptRegion.Value = strIDDepartement
End SubQuelques explications…
Cette procédure vérifie s'il y a une commune définie pour le site et le cas échéant, affecte la valeur du département et de la région en conséquence dans la zone de liste cboDeptRegion, du fait que celle-ci est indépendante et donc attachée à aucune source de contrôle.
Il est à noter que lors de la première utilisation, chacune des zones reste vide.
XIV. Les modules externes▲
Dans ce projet se trouvent deux modules :
- un dédié aux fonctions diverses ;
- l'autre dédié à encapsuler l'ensemble des fonctions et procédures liées directement aux formulaires.
XIV-A. VII-A. Le Module basFormsFunctions▲
XIV-A-1. L'en-tête de la feuille▲
L'en-tête ici ne comporte aucune constante ou variable.
'**********************************************************************
' Module : basFormsFunctions
' Type : Module
' DateTime : 30/07/2015
' Author : Argyronet
' Review : 23/09/2015 - Jean-Philippe AMBROSINO
' Review date : 27/11/2015
' Purpose : Module contenant les procédures et fonctions génériques [événementielles] des formulaires
'**********************************************************************
Option Compare Database
Option ExplicitXIV-A-2. La procédure événementielle FormCurrent▲
Cette procédure se substitue à la procédure événementielle Form_Current qui se traduit dans les propriétés événementielles par Sur activation.
Dans ce projet, cette procédure est uniquement utilisée au sein des formulaires.
On affecte à l'objet oSubForm le formulaire basé sur la constante SUBFORM_DATA_WIZ puisque tous les sous-formulaires portent le même nom, quel que soit le formulaire parent.
Ceci est plus commode pour la souplesse du code et de l'usage d'appels génériques d'objets comme les formulaires.
L'objet DoCmd exploite la méthode GoToRecord avec le paramètre acLast afin de positionner le sous-formulaire sur le dernier enregistrement.
Il donne alors le focus au contrôle FirstControl passé en paramètre.
Public Sub FormCurrent(ByRef TargetForm As Form, ByVal FirstControl As String)
'---------------------------------------------------------------------------
' Sub : FormCurrent
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Procédure générique de substitution de l'événement Current d'un formulaire simple
'...........................................................................
' Parameters : TargetForm : Formulaire cible
' FirstControl : Nom du 1er contrôle
' Return Codes : None
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim oSubForm As SubForm
Dim oControl As Control
With TargetForm
'On définit le sous-formulaire
Set oSubForm = .Form(SUBFORM_DATA_WIZ)
'On donne le focus au sous-formulaire
With oSubForm
.SetFocus
On Error Resume Next
'On pointe le dernier enregistrement s'il en existe (erreur optimiste)
DoCmd.GoToRecord , , acLast
'On définit le contrôle auquel donner le focus
Set oControl = .Controls(FirstControl)
oControl.SetFocus
End With
End With
'Libération des objets
Set oControl = Nothing
Set oSubForm = Nothing
End SubXIV-A-3. La procédure événementielle SubFormCurrent▲
Cette procédure se substitue elle aussi à la procédure événementielle Form_Current qui se traduit dans les propriétés événementielles par Sur activation.
Dans ce projet, cette procédure est uniquement utilisée au sein des sous-formulaires.
Comme il est fort probable qu'une erreur soit levée par le fait que le formulaire réagit en fonction des données, on pose une instruction d'erreur optimiste.
En effet, les fonctions de domaines sont susceptibles de renvoyer les erreurs à partir du moment où les champs ne permettent pas d'établir le calcul attendu.
On emploie alors l'instruction On Error Resume Next parce qu'ici, elle est maîtrisée.
Rappel
Vous ne devez employer cette instruction que si, et seulement si, vous en maîtrisez l'exécution.
Cet événement est produit lorsque le focus passe à un enregistrement donné pour en faire l'enregistrement activé, ou lorsque le formulaire est actualisé ou qu'il fait l'objet d'une nouvelle requête.
- La variable lngCount récupère la valeur du champ txtCountRows et la variable blnCompleted récupère la valeur du champ txtCompleted, tous deux situés dans le formulaire parent.
- Par prudence, et surtout pour les débutants, j'ai greffé l'interception de l'erreur 2147352567 afin de prévenir le développeur qu'il a oublié un truc quelque part…
Dans l'absolu, cette erreur ne devrait jamais être levée. - Si lngCount retourne une valeur, alors le contrôle étiquette lblCountRows reçoit une chaîne récapitulant le nombre d'enregistrements en genre et en nombre et sa couleur passe du vert au rouge selon que la ligne est complétée ou non.
Public Sub SubFormCurrent(ByRef TargetSubForm As Form, ByVal ItemData As String, ByVal IsFemale As Boolean)
'---------------------------------------------------------------------------
' Sub : SubFormCurrent
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Procédure générique de substitution de l'événement Current d'un formulaire simple
'...........................................................................
' Parameters : TargetForm : Formulaire cible
' ItemData : Sujet du contenu
' IsFemale : True pour changer le genre des textes
' Return Codes : None
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim lngCount As Long
Dim blnCompleted As Boolean
'On pose une gestion d'erreurs optimiste contrôlée
On Error Resume Next
'On compte le nombre d'enregistrements
lngCount = Nz(TargetSubForm.txtCountRows, 0)
'On vérifie si la ligne en cours est complète
blnCompleted = Nz(TargetSubForm.txtCompleted, False)
'Il est probable qu'une erreur définie par l'application ou par l'objet soit levée :
'on la gère de façon optimiste et n'en tient pas compte
If Err = -2147352567 Then
MsgBox "Attention..." & vbCrLf & "Vérifier que la source de contrôle du sous-formulaire '" & TargetSubForm.Name & "' est correcte !", vbExclamation, "Message développeur"
Err.Clear
End If
'S'il existe des enregistrements
If lngCount Then
With TargetSubForm.Parent
'On adapte l'intulé du label avec le nombre
.lblCountRows.Caption = lngCount & ItemData & "(s) enregistré" & IIf(IsFemale, "e", "") & "(s)"
With .lblRecCompleted
'On adapte l'intitulé du label avec le texte et la couleur correspondante
.Caption = "Ligne " & IIf(blnCompleted, "complète", "incomplète")
.ForeColor = IIf(blnCompleted, CLR_GREENCOLOR, CLR_REDCOLOR)
End With
.cmdDelete.Caption = CMD_DELETE
End With
End If
End SubXIV-A-4. La procédure OpenTheForm▲
Cette procédure publique permet d'ouvrir un formulaire de façon personnalisée.
Elle est utilisée ici dans la navigation depuis les boutons intitulés Précédent et Suivant avec par le paramètre GotoDirection.
Avant de fermer le formulaire en cours et ouvrir l'autre, selon que l'on ait affaire à un formulaire simple ou un sous-formulaire, les fonctions DataFormCompleted() ou DataSubFormCompleted() sont appelées afin d'avertir, par l'intermédiaire d'un MsgBox(),que les données ne sont pas complétées comme il se doit.
Du fait que ce message permette d'ignorer, l'utilisateur peut faire abstraction de cette alerte et continuer pour passer effectivement au formulaire qu'il a choisi, en cliquant respectivement sur le bouton approprié dans le sens de la navigation souhaitée, selon la valeur du paramètre GotoDirection.
Le formulaire courant est alors fermé :
- un examen du paramètre IsLastStep est effectué afin de vérifier si l'on est à la dernière étape ou non, auquel cas on ouvre le formulaire de fin frmEnd ;
- sinon, on ouvre le formulaire basé sur l'index.
Public Sub OpenTheForm(ByVal GotoDirection As ge_FormDirection, ByRef TargetForm As Form, ByVal NextFormID As Long, ByVal PreviousFormID As Long, ByVal TableSource As String, ByVal FieldCollection As String, ByVal FormWithSubForm As Boolean, Optional ByVal IsLastStep As Boolean = False)
'---------------------------------------------------------------------------
' Sub : OpenTheForm
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Procédure générique de substitution de la méthode OpenForm
'...........................................................................
' Parameters : GotoDirection : Sens (précédent/suivant)
' TargetForm : Formulaire cible
' NextFormID : ID du formulaire suivant
' PreviousFormID : ID du formulaire précédent
' TableSource : Table ou requête source
' FieldCollection : Noms des champs utilisés
' FormWithSubForm = True s'il y a un sous-formulaire
' Return Codes : None
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim SQLSelect As String
Dim intFormIndex As Integer
On Error GoTo L_ErrOpenTheForm
'On établit la clause Select fondée sur la table source
SQLSelect = "SELECT * FROM " & TableSource
'Si l'on vérifie le sous-formulaire...
If FormWithSubForm Then
'Si tous les champs du sous-formulaire ne sont pas renseignés...
If DataSubFormCompleted(TableSource, FieldCollection, False, NextFormID, IsLastStep) = vbNo Then Exit Sub
Else
'Si tous les champs du formulaire ne sont pas renseignés...
If DataFormCompleted(TargetForm, False, FieldCollection, NextFormID, IsLastStep) = vbNo Then Exit Sub
End If
'Selon le sens de navigation, on définit la variable de l'index du formulaire cible
If GotoDirection = GotoNext Then
intFormIndex = NextFormID
Else
intFormIndex = PreviousFormID
End If
'On ferme ce formulaire
DoCmd.Close acForm, TargetForm.Name
'Si l'on arrive à la dernière étape...
If IsLastStep Then
'Dernier identifiant : on ouvre le formulaire de fin
DoCmd.OpenForm FORM_END, , , , , acDialog
Else
'On ouvre le suivant ou précédent
If intFormIndex Then
DoCmd.OpenForm FORM_DATA_WIZ & intFormIndex, , , , , acDialog
Else
DoCmd.OpenForm FORM_START, , , , , acDialog
End If
End If
On Error GoTo 0
L_ExOpenTheForm:
Exit Sub
L_ErrOpenTheForm:
MsgBox Err.Description, vbExclamation, Err.Source
Resume L_ExOpenTheForm
End SubXIV-A-5. La fonction événementielle FormDataErrors()▲
Cette procédure générique se substitue de façon enrichie à l'événement Form_Error().
Du fait de la différence possible entre formulaire simple et sous-formulaire, cette fonction est indépendante.
On utilise ici un Select Case où l'on passe des conditions émises par des constantes dont les valeurs sont expressément les valeurs typiquement rencontrées dans cet événement.
Je suis bien conscient qu'il peut y en avoir d'autres, d'où le Case Else (à ne jamais oublier dans un tel bloc), mais pour ce projet tel que développé, cela n'entrait pas en considération.
La fonction retourne la valeur conséquente à la valeur logique de (Response = acDataErrContinue).
Public Function FormDataErrors(ByVal DataErr As Integer, ByRef Message As String, ByRef Response As Integer, Optional ByRef TargetForm As Form) As Boolean
'---------------------------------------------------------------------------
' Function : FormDataErrors
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Procédure générique de substitution de l'événement Form_Error
'...........................................................................
' Parameters : DataErr : Erreur de données
' Message : Message de retour
' Response : Flag de comportement de la réponse suite à l'erreur
' TargetForm : Formulaire cible
' Return Codes : Boolean = True if success
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Const RESET_VBA_CODE As Integer = 29013
Const INDEX_DOUBLONS As Integer = 3022
Const INPUT_MASK_VIOLATION As Integer = 2279
Const NOT_IN_LIST As Integer = 2237
Const INVALID_INPUTMASK As Integer = 2113
Message = vbNullString
Select Case DataErr
'Non-respect du masque de saisie
Case INPUT_MASK_VIOLATION
Response = acDataErrContinue
Message = "Attention !" & vbCrLf & "Le masque de saisie du champ doit être respecté."
'Élément absent de la liste
Case NOT_IN_LIST
Response = acDataErrContinue
Message = "Attention !" & vbCrLf & "Veuillez choisir un élément dans la liste..."
'Doublon ou tentative d'action de réinitialisation du code en cours d'exécution
Case INDEX_DOUBLONS, RESET_VBA_CODE
Response = acDataErrDisplay
Message = "Attention !" & vbCrLf & "Vous avez déjà saisi ou bien il existe un enregistrement identique dans la base !"
'Mauvais usage du masque de saisie
Case INVALID_INPUTMASK
Response = acDataErrContinue
Message = "Attention !" & vbCrLf & "La valeur est non valide pour ce champ..." & vbCrLf & vbCrLf & "Vous avez peut-être entré du texte dans un champ numérique ou un nombre supérieur à ce que permet la taille du champ ou encore une date incohérente."
'Toute autre cause...
Case Else
Response = acDataErrContinue
Message = "Attention !" & vbCrLf & "La valeur que vous avez tapée est incorrecte..."
If Not TargetForm Is Nothing Then
With TargetForm.ActiveControl
On Error Resume Next
'On annule le contenu du champ s'il est accessible
.Undo
Err.Clear
.SetFocus
End With
End If
End Select
'Valeur de retour
FormDataErrors = (Response = acDataErrContinue)
End FunctionXIV-A-6. La procédure événementielle SubFormDataError▲
Cette procédure générique se substitue de façon enrichie à l'événement Form_Error() et est écrite dans le même esprit que la fonction ci-avant FormDataErrors() à ceci près qu'elle est exploitée uniquement par les sous-formulaires.
Public Sub SubFormDataError(TargetForm As Form, DataErr As Integer, Response As Integer, ByVal TargetDataName As String)
'---------------------------------------------------------------------------
' Function : SubFormDataError
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Procédure générique de substitution de l'événement Form_Error pour les sous-formulaires
'...........................................................................
' Parameters : TargetForm : Formulaire cible
' DataErr : Erreur de données
' Response : Flag de comportement de la réponse suite à l'erreur
' TargetDataName : Sujet de la donnée concernée
'
' Return Codes : Boolean = True if success
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Const INVALID_INPUTMASK As Integer = 2113
Const INDEX_DOUBLONS As Integer = 3022
Const VALUE_IS_REQUIRED As Integer = 3314
Select Case DataErr
Case INVALID_INPUTMASK
'Ne devrait jamais arriver sauf si vous avez inversé la colonne liée
MsgBox "Attention : " & vbCrLf & "Valeur non valide pour ce champ.Vous avez peut-être entré du texte dans un champ numérique ou un nombre supérieur à ce que permet sa taille.", vbExclamation, "Valeur incompatible"
Case VALUE_IS_REQUIRED
'Donnée requise
If MsgBox("Vous devez d'abord inscrire une valeur dans chaque champ." & vbCrLf & vbCrLf & "Voulez-vous annuler cet enregistrement ?", 52, "Ligne incomplète") = 6 Then
On Error Resume Next
DoCmd.RunCommand acCmdUndo
On Error GoTo 0
With TargetForm.Parent
.cmdAdd.Caption = CMD_ADD
.cmdDelete.Caption = CMD_DELETE
End With
End If
Case INDEX_DOUBLONS
'Risque de doublons dans champs index
MsgBox "Attention : " & vbCrLf & "Vous avez créé un doublon :" & vbCrLf & "(" & TargetDataName & ")." & vbCrLf & "Veuillez modifier votre saisie ou bien annuler la ligne (Echap).", vbExclamation, "Ligne dupliquée"
Case Else
'Toute autre raison
MsgBox "Attention : " & vbCrLf & "Veuillez d'abord compléter la ligne avant de passer à une autre ou d'effectuer une autre opération.", vbExclamation, "Action non conventionnelle"
End Select
Response = acDataErrContinue
End SubXIV-A-7. La fonction publique DataFormCompleted()▲
Cette fonction permet de vérifier qu'un formulaire simple est correctement alimenté, sous-entendu, qu'il ne manque pas une valeur dans un des champs.
Cette fonction parcourt tous les champs du formulaire afin d'en vérifier la nullité.
Pour ce faire, on utilise un tableau de Strings alimenté avec la collection de champs et on affecte chacun d'eux à une variable objet de type Control.
Rappel
Tous les champs des formulaires simples voient leur propriété Tag définie à Null ou NotNull :
- Null : la valeur vide ou nulle est autorisée :
- NotNull : la valeur vide ou nulle est interdite.
Il est vrai que l'on aurait pu exploiter la propriété Null Interdit issue de la table, mais je pars du principe que pour ce projet, c'était démesuré et cela aurait nécessité du code un peu plus complexe pour finalement, aboutir au même résultat.
Illustration de la propriété :
Une erreur est levée à partir du moment où la nullité est rencontrée…
- Si tout va bien que l'analyse aboutit, cette procédure appelle FillDataChecker afin d'enrichir le tableau g_intCompletionIndex des étapes.
- Si elle échoue, sous-entendu qu'il manque une valeur dans un des champs, la fonction retourne un MsgBox où la question autorise malgré tout à continuer (opération voulue expressément dans le contexte de ce projet puisque l'on considère que l'opérateur peut très bien ne pas connaître l'information manquante).
Public Function DataFormCompleted(ByRef TargetForm As Form, ByVal AddNewRecord As Boolean, ByVal FieldControlCollection As String, ByVal NextFormID As Integer, Optional ByVal IsLastStep As Boolean = False) As VbMsgBoxResult
'---------------------------------------------------------------------------
' Function : DataFormCompleted
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Vérifie que les données du formulaire sont complétées
'...........................................................................
' Parameters : TargetForm : Formulaire cible
' AddNewRecord : True si mode ajout
' FieldControlCollection : Liste des champs utilisés
' NextFormID : ID du formulaire suivant
' Return Codes : VbMsgBoxResult = Réponse au Msgbox
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Const ADD_MESSAGE As String = "Avant d'ajouter un nouvel enregistrement, vous devez faire en sorte que l'enregistrement en cours soit complété."
Const ERR_MESSAGE As String = "Avant de changer d'écran, vous devez faire en sorte que ce formulaire possède au moins un enregistrement représentant un site."
Const IGNORE_MESSAGE As String = vbCrLf & vbCrLf & "Voulez-vous ignorer ce message et continuer ?"
Dim strMessage As String
Dim straFieldControlCollection() As String
Dim D As Integer
Dim oControl As Control
On Error GoTo L_ErrDataFormCompleted
'On affecte le bon message selon le mode
If AddNewRecord Then
strMessage = ADD_MESSAGE & IGNORE_MESSAGE
Else
strMessage = ERR_MESSAGE & IGNORE_MESSAGE
End If
'Affectation du tableau de champs
straFieldControlCollection = Split(FieldControlCollection, ";")
'Pour chacun des champs
For D = LBound(straFieldControlCollection) To UBound(straFieldControlCollection)
'On affecte l'objet au contrôle du formulaire
Set oControl = TargetForm.Controls(straFieldControlCollection(D))
With oControl
'S'il est nul
If IsNull(.Value) Then
'Si la propriété Remarque spécifie l'interdiction du Null
If .Tag = "NotNull" Then
'On lève une erreur
Err.Raise 94, "Donnée requise", strMessage
End If
End If
End With
Next
'On enrichit le tableau récapitulatif de vérification
If IsLastStep Then
'Si c'est la dernière, on prend l'index de la constante
Call FillDataChecker(MAX_STEP_WIZ, True)
Else
'Celle en cours
Call FillDataChecker(NextFormID - 1, True)
End If
DataFormCompleted = True
On Error GoTo 0
L_ExDataFormCompleted:
Set oControl = Nothing
Exit Function
L_ErrDataFormCompleted:
'En cas d'erreur (optimiste, on vérifie les data), la fonction retourne un MsgBox
DataFormCompleted = MsgBox(Err.Description, vbExclamation + vbYesNo, Err.Source)
'On décrémente le tableau récapitulatif de vérification pour cette étape
If Not IsLastStep Then
Call FillDataChecker(NextFormID - 1, False)
End If
Resume L_ExDataFormCompleted
End FunctionXIV-A-8. La fonction publique DataSubFormCompleted()▲
Cette fonction permet de vérifier qu'un sous-formulaire est correctement alimenté, sous-entendu, qu'il ne manque pas une valeur dans un des champs.
Elle appelle la fonction privée VerifyFormDataTable() afin de lire chaque enregistrement, champ par champ.
- Si tout va bien que l'analyse aboutit, cette procédure appelle FillDataChecker afin d'enrichir le tableau g_intCompletionIndex des étapes.
- Si elle échoue, sous-entendu qu'il manque une valeur quelque part, la fonction retourne un MsgBox où la question autorise malgré tout à continuer (opération voulue expressément dans le contexte de ce projet puisque l'on considère que l'opérateur peut très bien ne pas connaître l'information manquante).
Public Function DataSubFormCompleted(ByVal TargetTable As String, ByVal FieldCollection As String, ByVal AddNewRecord As Boolean, ByVal NextFormID As Integer, Optional ByVal IsLastStep As Boolean = False) As VbMsgBoxResult
'---------------------------------------------------------------------------
' Function : DataSubFormCompleted
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Vérifie que les données du sous-formulaire sont complétées
'...........................................................................
' Parameters : TargetTable : Table source
' FieldControlCollection : Liste des champs utilisés
' AddNewRecord : True si mode ajout
' NextFormID : ID du formulaire suivant
' Return Codes : VbMsgBoxResult = Réponse au Msgbox
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Const ADD_MESSAGE As String = "Avant d'ajouter un nouvel enregistrement, vous devez faire en sorte que l'enregistrement en cours soit complété."
Const ERR_MESSAGE As String = "Avant de changer d'écran, vous devez faire en sorte que : " & vbCrLf & " - que l'enregistrement en cours soit complété." & vbCrLf & " - le formulaire possède au moins un enregistrement."
Const IGNORE_MESSAGE As String = vbCrLf & vbCrLf & "Voulez-vous ignorer ce message et continuer ?"
Dim strMessage As String
Dim blnAddNew As Boolean
On Error GoTo L_ErrDataSubFormCompleted
'On affecte le bon message selon le mode
If AddNewRecord Then
strMessage = ADD_MESSAGE & IGNORE_MESSAGE
Else
strMessage = ERR_MESSAGE & IGNORE_MESSAGE
End If
'On appelle la fonction de vérification des données dans la table
If VerifyFormDataTable(TargetTable, FieldCollection) = False Then
Err.Raise 94, "Données requises", strMessage
Else
'On enrichit le tableau récapitulatif de vérification
If IsLastStep Then
'Si c'est la dernière, on prend l'index de la constante
Call FillDataChecker(MAX_STEP_WIZ, True)
Else
'Celle en cours
Call FillDataChecker(NextFormID - 1, True)
End If
End If
On Error GoTo 0
L_ExDataSubFormCompleted:
Exit Function
L_ErrDataSubFormCompleted:
'En cas d'erreur (optimiste, on vérifie les data), la fonction retourne un MsgBox
DataSubFormCompleted = MsgBox(Err.Description, vbExclamation + vbYesNo, Err.Source)
'On décrémente le tableau récapitulatif de vérification pour cette étape
If NextFormID <> LAST_FORM_INDEX Then
Call FillDataChecker(NextFormID - 1, False)
End If
Resume L_ExDataSubFormCompleted
End FunctionXIV-A-9. La fonction privée VerifyFormDataTable()▲
Cette fonction permet de vérifier la complétude de la table source et la collection de champs de cette table utilisée dans les sous-formulaires (certes, on aurait pu parcourir cette collection via l'objet Fields, mais ce n'est pas nécessaire ici).
Private Function VerifyFormDataTable(ByVal TargetTable As String, ByVal FieldCollection As String) As Boolean
'---------------------------------------------------------------------------
' Function : VerifyFormDataTable
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Contrôle champ par champ la nullité du contenu
'...........................................................................
' Parameters : TargetTable : Table ou requête source
' FieldCollection : Champs de la source
' Return Codes : Boolean = True if success
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim SQLSelect As String
Dim oRS As DAO.Recordset
Dim straFields() As String
Dim strFieldName As String
Dim vntValue As Variant
Dim F As Integer
Dim lngNBRecords As Long
Dim lngRowCount As Long
Dim blnDataMissing As Boolean
On Error GoTo L_ErrVerifyFormDataTable
blnDataMissing = False
'Clause SQL sélectionnant tous les enregistrements de la table
SQLSelect = "SELECT * FROM " & TargetTable & ";"
'Ouverture du Recordset
Set oRS = CurrentDb.OpenRecordset(SQLSelect, 2)
'Affectation du tableau de champs
straFields = Split(FieldCollection, ";")
With oRS
If Not .EOF Then
.MoveLast
'On compte le nombre d'enregistrements
lngNBRecords = .RecordCount
.MoveFirst
Do While Not .EOF
lngRowCount = lngRowCount + 1
'Pour chacun des champs
For F = LBound(straFields) To UBound(straFields)
'On attribue son nom à la variable
strFieldName = straFields(F)
'On verifie s'il est null
If IsNull(.Fields(strFieldName)) Then
'Auquel cas, on sort en attribuant True à la variable
blnDataMissing = True
Exit For
End If
Next
'À partir du moment où la variable est True, il est inutile de continuer, il y a une omission.
If blnDataMissing Then
Exit Do
End If
.MoveNext
Loop
Else
'S'il n'y a aucun enregistrement
Err.Raise 3021
End If
'On ferme le Recordset
.Close
End With
'La fonction retourne la négation logique de la valeur de la variable
VerifyFormDataTable = Not blnDataMissing
On Error GoTo 0
L_ExVerifyFormDataTable:
'On libère l'objet
Set oRS = Nothing
Exit Function
L_ErrVerifyFormDataTable:
VerifyFormDataTable = False
Resume L_ExVerifyFormDataTable
End FunctionXIV-A-10. La procédure publique AddNewOrCancel▲
Cette procédure permet d'ajouter ou d'annuler un enregistrement selon l'intitulé du bouton passé en paramètre par le biais de l'objet Form.
On lui passe l'index du sous-formulaire, le nom de la table source, la collection de champs de cette table, le nom du premier contrôle devant être focalisé, la valeur de l'ID principal (ici celui du site), la valeur de l'index du prochain formulaire et la valeur permettant de savoir si on arrive à la dernière étape…
-
Si le bouton porte l'intitulé Ajouter :
- s'il s'agit d'un sous-formulaire, on ajoute une ligne vide via la méthode GoToRecord avec le paramètre acNewRec de l'objet DoCmd ;
- s'il s'agit d'un formulaire simple, on appelle la fonction DataFormCompleted() afin d'empêcher l'ajout s'il n'est pas complété.
- Si le bouton porte l'intitulé Annuler, on invoque un simple RunCommand avec le paramètre acCmdUndo de l'objet DoCmd.
Public Sub AddNewOrCancel(ByRef TargetForm As Form, ByVal SubFormIndex As Integer, ByVal TableSource As String, ByVal FieldCollection As String, ByVal FirstControl As String, ByVal SharedForeignIdentity As Boolean, ByVal NextFormID As Integer, Optional ByVal IsLastStep As Boolean = False)
'---------------------------------------------------------------------------
' Sub : AddNewOrCancel
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Double procédure permettant d'ajouter un nouvel enregistrement ou d'annuler celui en cours de saisie
'...........................................................................
' Parameters : TargetForm : Formulaire cible
' SubFormIndex : Si <> 0 signifie qu'il y a un sous-formulaire
' TableSource : Table ou requête source
' FieldCollection : Liste des champs de la source
' FirstControl : Nom du 1er contrôle
' SharedForeignIdentity : True si la source contient une valeur primaire commune (ici NoSite)
' NextFormID = ID du formulaire suivant
' Return Codes : None
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim oSubForm As Form
With TargetForm
'Si la légende est {Annuler}
Select Case .cmdAdd.Caption
Case CMD_CANCEL
'On demande confirmation de l'annulation
If MsgBox("Voulez-vous annuler l'ajout en cours ?", vbQuestion + vbYesNo, "Annuler") = vbYes Then
'On pose une gestion d'erreurs optimiste
On Error Resume Next
'On appelle la méthode RunCommand de la constante AcCommand permettant l'annulation en cours
DoCmd.RunCommand acCmdUndo
'On réinitialise le gestionnaire d'erreurs
On Error GoTo 0
'On change l'intitulé des boutons
.cmdAdd.Caption = CMD_ADD
.cmdDelete.Caption = CMD_DELETE
End If
'Si la légende est {Ajouter}
Case CMD_ADD
'On met à jour les données sous-jacentes du formulaire en actualisant les modifications apportées aux enregistrements
TargetForm.Requery
'S'il y existe un index, on affecte l'objet oSubForm au sous-formulaire
'(SubFormIndex=0 s'il n'y a pas de sous-formulaire)
If SubFormIndex Then
'On affecte l'objet à ce sous-formulaire
Set oSubForm = Forms(FORM_DATA_WIZ & SubFormIndex)
With oSubForm
'On donne le focus au sous-formulaire
.SetFocus
On Error Resume Next
DoCmd.GoToRecord , , acNewRec
'Si la table contient le N° du site...
If SharedForeignIdentity Then
'On affecte au champ (ici : [NoSite]), la valeur de la variable publique
'qui contient sa valeur en cours.
.Form.NoSite = g_strNoSiteEnCours
End If
'On donne le focus au premier contrôle disponible
.Form.Controls(FirstControl).SetFocus
'On réinitialise le gestionnaire d'erreurs
On Error GoTo 0
End With
Else
'On appelle la fonction interne DataFormCompleted() afin de vérifier si l'ajout est possible
If DataFormCompleted(TargetForm, False, FieldCollection, NextFormID, IsLastStep) = vbNo Then Exit Sub
End If
'On change l'intitulé des boutons
.cmdAdd.Caption = CMD_CANCEL
.cmdDelete.Caption = CMD_SAVE
End Select
End With
'On libère l'objet...
Set oSubForm = Nothing
End SubXIV-A-11. La procédure publique DeleteOrSaveRecord▲
Cette procédure permet de supprimer ou de sauvegarder un enregistrement selon l'intitulé du bouton passé en paramètre par le biais de l'objet Form.
On lui passe l'index du sous-formulaire, la valeur cible (ID), la clause SQL de suppression et le sujet illustrant le contexte.
- Si le bouton porte l'intitulé Supprimer, on invoque un Excecute de l'objet CurrentDB.
- Si le bouton porte l'intitulé Sauvegarder, on invoque un simple Requery.
Public Sub DeleteOrSaveRecord(ByRef TargetForm As Form, ByVal SubFormIndex As Integer, ByVal TargetIDValue As Variant, ByVal DeleteClause As String, ByVal Subject As String)
'---------------------------------------------------------------------------
' Sub : DeleteOrSaveRecord
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Double procédure permettant d'effacer un enregistrement ou de le sauvegarder
'...........................................................................
' Parameters : TargetForm : Formulaire cible
' SubFormIndex : Si <> 0, on agit sur le sous-formulaire
' TargetIDValue : Valeur de l'identifiant
' DeleteClause : Clause SQL DELETE FROM
' Subject : Sujet de l'élément à supprimer
' Return Codes : = True if success
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim oSubForm As Form
On Error GoTo L_ErrDeleteOrSaveRecord
'S'il y existe un index (SubFormIndex=0 s'il n'y a pas de sous-formulaire)
If SubFormIndex Then
'On affecte l'objet à ce sous-formulaire
Set oSubForm = TargetForm.Form(FORM_DATA_WIZ & SubFormIndex)
End If
Select Case TargetForm.cmdDelete.Caption
'Si la légende est {Supprimer}
Case CMD_DELETE
'Et si l'identifiant n'est pas nul
If Not IsNull(TargetIDValue) Then
'On confirme la suppression
If MsgBox("Êtes-vous certain de vouloir supprimer " & Subject & " ?" & vbCrLf & vbCrLf & "Cette action est irréversible.", 292, "Supprimer un employé") = 6 Then
'On exécute la requête DELETE
CurrentDb.Execute DeleteClause, dbFailOnError
'Si c'est le sous-formulaire
If SubFormIndex Then
oSubForm.Requery
Else
TargetForm.Requery
End If
End If
Else
'Si l'identifiant est nul, on lève une erreur
Err.Raise 94, "Pas d'identifiant", "Il n'y a aucun enregistrement sélectionné qui puisse être supprimé !"
End If
'On change l'intitulé des boutons
TargetForm.cmdDelete.Caption = CMD_DELETE
TargetForm.cmdAdd.Caption = CMD_SAVE
'Si la légende est {Sauvegarder}
Case CMD_SAVE
'Si c'est le sous-formulaire
If SubFormIndex Then
oSubForm.Requery
Else
TargetForm.Requery
End If
'On change l'intitulé des boutons
TargetForm.cmdAdd.Caption = CMD_ADD
TargetForm.cmdDelete.Caption = CMD_DELETE
End Select
On Error GoTo 0
L_ExDeleteOrSaveRecord:
'On libère l'objet...
Set oSubForm = Nothing
Exit Sub
L_ErrDeleteOrSaveRecord:
MsgBox Err.Description, vbExclamation, Err.Source
Resume L_ExDeleteOrSaveRecord
End SubXIV-A-12. La fonction publique DataTableHasRecord()▲
Cette fonction permet de savoir si une table ou plus exactement ici, une clause SQL fondée sur une table, contient des enregistrements.
Grâce à cette fonction, un message s'affiche en tant qu'alerte pour avertir que le formulaire doit contenir au moins un enregistrement.
Private Function DataTableHasRecord(ByVal SQLQuery As String) As Boolean
'---------------------------------------------------------------------------
' Function : DataTableHasRecord
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Vérifie que la source SQLQuery possède des enregistrements
'...........................................................................
' Parameters : SQLQuery :
' Return Codes : Boolean = True if has data
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim oRS As DAO.Recordset
'On initialise l'objet Recordset avec la chaîne SQL passée en paramètre
Set oRS = CurrentDb.OpenRecordset(SQLQuery, 2)
With oRS
'On renvoie la valeur correspondant à la position d'enregistrement actuelle :
'False = aucun enregistrement
DataTableHasRecord = Not .EOF
.Close
End With
Set oRS = Nothing
End FunctionXIV-A-13. La fonction publique NullData()▲
Cette fonction est utilisée dans tous les sous-formulaires afin de calculer la nullité des champs.
Sur le principe, elle retourne 0 si le champ est Null et 1 s'il ne l'est pas, et ce, quel que soit son type.
Remarque
Le type (DataType) ici est défini à l'image des constantes dbText (10), dbLong (4) et dbDate (8) de façon générique.
Au sein de la formule qui l'exploite dans le contrôle txtCheckdata, cette fonction est additionnée à elle-même pour établir une somme d'entiers.
Ainsi, la case à cocher chkCompleted prend la valeur True si la valeur finale de ce champ correspond à la valeur attendue.
Public Function NullData(ByVal Ctl As Variant, ByVal DataType As Integer) As Integer
'---------------------------------------------------------------------------
' Function : NullData
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Retourne 1 ou 0 selon la nullité du contenu
'...........................................................................
' Parameters : Ctl : contrôle de formulaire cible
' DataType : Type de donnée du contrôle
' Return Codes : Integer = True if success
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
On Error GoTo L_ErrNullData
NullData = 0
If Not IsNull(Ctl) Then
NullData = 1
End If
Select Case DataType
Case 10
If Len(Nz(Ctl, "")) Then
NullData = 1
End If
Case 4
If (Ctl > 0) Then
NullData = 1
ElseIf (Ctl = 0) And (Len(Ctl) < 2) Then
NullData = 0
Else
NullData = Nz(Ctl, 0)
End If
Case 8
If IsDate(Ctl) Then
NullData = 1
End If
Case Else
End Select
On Error GoTo 0
L_ExNullData:
Exit Function
L_ErrNullData:
NullData = 0
Resume L_ExNullData
End FunctionXIV-A-14. La fonction événementielle publique NotInList()▲
Cette fonction est une réplique générique associée à la rédaction typique de l'événement NotInList() où l'on force le paramètre Response à acDataErrContinue accompagné d'un message personnalisé sans pour autant permettre un ajout en cas de valeur manquante, car cela ne s'y prête pas.
Le message est construit dynamiquement avec le contenu du paramètre ItemSubject.
Vous remarquerez d'ailleurs que l'on passe ici le paramètre NewData et que la valeur Response est la valeur de retour.
Public Function NotInList(ByVal NewData As String, ByVal ItemSubject As String) As Integer
'---------------------------------------------------------------------------
' Function : NotInList
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Fonction générique de substitution de l'événement NotInList d'un ComboBox
'...........................................................................
' Parameters : NewData : Valeur que l'utilisateur a entrée dans la zone de liste modifiable.
' ItemSubject : Sujet de la liste déroulante
' Return Codes : Integer = Valeur la fonction NotInList
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
MsgBox ItemSubject & " '" & NewData & "' ne fait pas partie de la liste !", vbExclamation, "Choix incorrect"
NotInList = acDataErrContinue
End FunctionXIV-B. VII-A. Le Module basMain▲
XIV-B-1. L'en-tête de la feuille▲
L'en-tête ici ne comporte pas un grand jeu de constantes, quelques variables et une énumération.
- Les constantes de toutes catégories concernent l'affichage des contrôles masqués, de la définition des étapes et d'index de formulaire, des noms génériques des formulaires, des légendes des boutons et des couleurs des intitulés.
- L'énumération définit les valeurs de direction de la navigation pour les boutons appelant la méthode OpenTheForm avec le paramètre précédent ou suivant…
- Les variables publiques * sont commentées dans le code.
* On n'utilise habituellement pas et par préférence ce type de variable, mais pour un projet de cette envergure et qualifié de jetable dans le jargon des professions en rapport, cela n'a pas grande importance.
'**********************************************************************
' Module : basMain
' Type : Module
' DateTime : 30/07/2015
' Author : Argyronet
' Review : 23/09/2015 - Jean-Philippe AMBROSINO
' Review date : 27/11/2015
' Purpose : Tutoriel : Réalisez un assistant de présaisie des données pour alimenter la base d'un serveur central
'
'**********************************************************************
Option Compare Database
Option Explicit
'Affichage ou masquage des contrôles : Prod = False ; Test = True
Public Const SHOW_HIDDEN_CONTROLS As Boolean = False
'Constantes de données
Public Const MAX_STEP_WIZ As Integer = 5
Public Const LAST_FORM_INDEX As Integer = 99
'Constantes des noms de formulaire
Public Const FORM_DATA_WIZ As String = "frmDataWizard"
Public Const SUBFORM_DATA_WIZ As String = "sfrmDataWizard"
Public Const SUB_FORM_SUFFIX As String = "_sub"
Public Const FORM_START As String = "frmStart"
Public Const FORM_END As String = "frmEnd"
'Constantes des boutons de commandes
Public Const CMD_STOP As String = "&Arrêter"
Public Const CMD_CLOSE As String = "&Fermer"
Public Const CMD_ADD As String = "Aj&outer"
Public Const CMD_SAVE As String = "S&auvegarder"
Public Const CMD_VALIDATE As String = "V&alider"
Public Const CMD_MODIFY As String = "M&odifier"
Public Const CMD_CANCEL As String = "&Annuler"
Public Const CMD_DELETE As String = "S&upprimer"
Public Const CMD_CANCEL_3DOTS As String = CMD_CANCEL & "..."
Public Const CMD_RESTART As String = "&Recommencer..."
Public Const CLR_REDCOLOR As Long = 255
Public Const CLR_GREENCOLOR As Long = 39168
'Énumération qui définit le sens d'ouverture des formulaires
Public Enum ge_FormDirection
GotoNext = 1
GotoPrevious = 3
End Enum
'Variables publiques :
'No du site et nom du domaine exploités par la session en cours
Public g_strNoSiteEnCours As String
Public g_strDomaineSiteEnCours As String
'Liste des étapes incomplètes
Public g_strDataSteps As String
'Tableau des N° d'étapes complétées ou non
Public g_intCompletionIndex(1 To MAX_STEP_WIZ) As IntegerXIV-B-2. La procédure publique FillDataChecker▲
Cette procédure permet d'alimenter le tableau d'entiers avec une valeur True ou False à l'Index selon la valeur de Completed.
Public Sub FillDataChecker(ByVal Index As Integer, ByVal Completed As Boolean)
'---------------------------------------------------------------------------
' Sub : FillDataChecker
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Alimente le tableau g_intCompletionIndex selon l'index passé
'...........................................................................
' Parameters : Index : Index du tableau
' Completed : True si complété
' Return Codes : None
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
g_intCompletionIndex(Index) = Completed
End SubXIV-B-3. La fonction publique AllStepsAreCompleted()▲
Cette fonction permet de vérifier les étapes qui sont complétées ou non et d'en établir une liste qui est fournie à la variable g_strDataPage.
Public Function AllStepsAreCompleted(ByRef Count As Integer) As Boolean
'---------------------------------------------------------------------------
' Function : AllStepsAreCompleted
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Vérifie les étapes complétées
'...........................................................................
' Parameters : Count : Valeur représentant le nombre d'étapes
' Return Codes : Boolean = True if success
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim P As Integer
Dim intStepCount As Integer
Dim strDataPage As String
intStepCount = 0
strDataPage = ""
'Pour chacune des étapes
For P = LBound(g_intCompletionIndex) To UBound(g_intCompletionIndex)
'Si elle a été affectée à True via FillDataChecker()
If (g_intCompletionIndex(P) = True) Then
'On incrémente le nombre d'étapes valides
intStepCount = intStepCount + 1
Else
'Sinon, on remplit une chaîne stipulant les étapes ignorées
strDataPage = strDataPage & " - étape N° " & P & vbCrLf
End If
Next
Count = intStepCount
'La fonction est True si le nombre d'étapes complétées est égal au nombre total d'étapes
AllStepsAreCompleted = (Count = MAX_STEP_WIZ)
'Définit le message final
If Len(strDataPage) Then
g_strDataPage = "Liste des étapes incomplètes :" & vbCrLf & vbCrLf & strDataPage
Else
g_strDataPage = "Liste des étapes incomplètes :" & vbCrLf & vbCrLf & "Aucune"
End If
End FunctionXIV-B-4. La fonction publique EraseAllDataBefore()▲
Cette fonction permet la remise à blanc des tables de données utilisateur.
Elle est appelée au début du lancement lorsque la case à cocher sur l'écran d'accueil est cochée.
Public Function EraseAllDataBefore(ByRef ErrDescription As String) As Boolean
'---------------------------------------------------------------------------
' Function : EraseAllDataBefore
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Fonction de RAZ des données occultant certaines tables
'...........................................................................
' Parameters : ErrDescription = Message d'erreur
' Return Codes : Boolean = True if success
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Const TABLE_TO_IGNORE As String = "tbl_ListeDepartements;tbl_ListeDesCommunes;tbl_ListeDesRegions;tbl_Privileges;tbl_TypesContrat"
Const CHAR_SEP As String = ";"
Const DELETE_DATA As String = "DELETE * FROM #T;"
Dim oDB As DAO.Database
Dim strListOfTables As String
Dim straTables() As String
Dim SQLDelete As String
Dim T As Integer
On Error GoTo L_ErrEraseAllDataBefore
strListOfTables = GetListOfTables(TABLE_TO_IGNORE, CHAR_SEP)
Set oDB = CurrentDb
With oDB
Debug.Print strListOfTables
straTables = Split(strListOfTables, CHAR_SEP)
For T = LBound(straTables) To UBound(straTables)
SQLDelete = Replace(DELETE_DATA, "#T", straTables(T))
.Execute SQLDelete, dbFailOnError
Next
.Close
End With
EraseAllDataBefore = True
On Error GoTo 0
L_ExEraseAllDataBefore:
Set oDB = Nothing
Exit Function
L_ErrEraseAllDataBefore:
EraseAllDataBefore = False
'Message d'erreur avec N°
ErrDescription = Err.Description & " - (" & Err.Number & ")"
Resume L_ExEraseAllDataBefore
End FunctionXIV-B-5. La fonction privée GetListOfTables()▲
Cette fonction établit la liste des tables devant être remises à blanc.
Elle est appelée par la fonction EraseAllDataBefore() ci-avant.
Private Function GetListOfTables(ByVal ExcludeTables As String, Optional ByVal CharSep As String = ";") As String
'---------------------------------------------------------------------------
' Function : GetListOfTables
' DateTime : 27/11/2015
' Author : Jean-Philippe AMBROSINO : Argyronet sur developpez.com (Code du tutoriel 11-2015)
' Purpose : Retourne la liste des tables devant être vidées
'...........................................................................
' Parameters : ExcludeTables : Liste des tables à exclure
' Return Codes : String = Liste des tables
'...........................................................................
' Notice :
'---------------------------------------------------------------------------
Dim oDB As DAO.Database
Dim oTDF As DAO.TableDef
Dim strDataTables As String
Dim straExcludeTables() As String
Dim strExcludeTable As String
Dim T As Integer
Set oDB = CurrentDb
straExcludeTables = Split(ExcludeTables, CharSep)
'Énumère toutes les tables sauf les tables système
For Each oTDF In oDB.TableDefs
With oTDF
If .Attributes = 0 And Left$(.Name, 4) <> "MSys" Then
'On incrémente la chaîne avec le nom des tables
strDataTables = strDataTables & Trim$(.Name) & CharSep
End If
End With
Next
'On ferme la base instanciée
oDB.Close
Set oDB = Nothing
'On parcourt le tableau des tables à exclure
For T = LBound(straExcludeTables) To UBound(straExcludeTables)
strExcludeTable = straExcludeTables(T)
'On remplace chaque table à exclure et son ; par une chaîne vide
strDataTables = Replace(strDataTables, strExcludeTable & CharSep, "")
Next
'On élimine le dernier caractère et on affecte le tout comme valeur de retour
If Right(strDataTables, 1) = CharSep Then
GetListOfTables = Left$(strDataTables, Len(strDataTables) - 1)
End If
End FunctionXV. Mise en exécution▲
Lorsque vous pensez que vous avez pu mettre en œuvre ce projet en l'état, il doit être prêt à tourner.
Vous avez le choix de lancer indifféremment :
- le formulaire frmStart ;
- la macro AutoExec ;
- la procédure de démarrage que vous auriez créé (non présente dans le code, ici) ;
- La fermeture d'Access et ouvrir l'application directement via un raccourci, par exemple.
Le premier formulaire s'affiche et vous pouvez commencer les tests de saisie et de comportement pour voir si vous n'avez rien omis ou bien si l'interfaçage vous satisfait ; vous pouvez alors selon vos constats, améliorer ou modifier des éléments que vous jugeriez nécessaires.
XV-A. En images, l'exécution peut se dérouler ainsi▲
L'écran d'accueil est affiché et on souhaite remettre à blanc les données puisque la case cochée…
Un message de confirmation est alors affiché :
- si vous cliquez sur Oui toutes les données sont effacées de façon irréversible et vous passez à l'étape N° 1 ;
- si vous cliquez sur Non, vous passez à l'étape N° 1 sans effacer les données ;
- si vous cliquez sur Annuler, vous restez sur cet écran.

À l'étape N° 1, on définit le nom du site.
Dans ce projet, il est considéré (comme cela l'est stipulé dans le texte en rouge) que le site est unique et qu'il reste en référence dans toutes les tables sous-jacentes.
Si toutefois vous tentez de passer à l'étape suivante alors que le formulaire n'est pas du tout rempli, le message suivant fait son apparition.

Comme je vous l'ai déjà précisé, les messages d'erreurs sont qualifiés d'optimistes.
Dans l'absolu, pour cette première étape, on aurait dû rendre le message non permissif et interdire la non-saisie du site, puisque c'est le site qui fait office de référence.
Mais il est considéré ici que c'est juste pour la démonstration et que vous pouvez modifier le comportement du message en conséquence de l'appel source.
Si toutefois vous vouliez modifier le message, il faudrait remplacer les boutons vbYesNo (Oui - Non) par le bouton OK et interdire le passage au formulaire suivant.
De là plusieurs options s'offrent à vous :
- griser le bouton suivant tant que les données ne sont pas remplies ;
- modifier le message de telle sorte qu'il affiche un bouton OK.
Une fois que vous avez rempli toutes les données de l'étape N° 1 vous passez à l'étape N° 2 pour alimenter l'ensemble du personnel du site.
Le code inscrit dans ce formulaire permet de transformer les prénoms en Nom Propre et les noms de famille en MAJUSCULES.
Par ailleurs, l'adresse e-mail est composée automatiquement sur le principe déjà expliqué dans les pages précédentes et notamment ici.
Vous ne pouvez pas remarquer ici l'effet clignotant de l'étiquette mentionnant que la ligne est complétée puisque c'est une image. Mais dans la vidéo un peu plus bas, cet effet est tout à fait visible.
L'étape suivante N° 3 va vous permettre de saisir les communes exploitées par le site.
Le principe est simple, vous choisissez le département et aussitôt la liste de droite se filtre sur les communes de ce dernier. Il suffit alors de choisir la bonne commune.
Vous pouvez remarquer qu'en cours de saisie, la mention ligne incomplète est affichée tout simplement parce que l'opérateur est en train de la remplir.
Si à ce stade il tente de passer à l'étape suivante, le message d'alerte fait son apparition en lui proposant d'ignorer ou de continuer.

Il propose alors :
- soit de finir la saisie (pas le choix, et toc !) ;
- soit d'annuler la saisie en cours…
À l'étape N° 4, le formulaire propose de saisir un ou plusieurs contrats.
Tous les champs ici sont obligatoires.
Si toutefois il existe plusieurs contrats déjà enregistrés, le bouton permettant d'en afficher la liste est disponible.
L'avant-dernière étape N° 5 est dédiée à l'alimentation des communes affectées à certains contrats.
Dans la liste de gauche figurent les contrats que vous avez déjà enregistrés, et dans la liste de droite, les communes supervisées par le site.
Attention
Il n'a pas été prévu ici de code inhérent au comportement des données s'il manque des informations (site, contrat, etc.).
Dans la mesure où vous avez complété toutes les étapes, le dernier écran vous spécifie qu'aucune étape n'a été omise…
Il décrit alors au sein de ce formulaire la marche à suivre.
Ci-dessous, par exemple, on peut constater que les étapes N° 1 et N° 2 sont incomplètes.
XVI. Mise en pratique du projet▲
Ainsi que cela déjà été élaboré au début de ce tutoriel, l'objectif de ce projet est de fournir une application prête à l'emploi dont l'objectif est de collecter l'ensemble des données que vous souhaitez centraliser dans une seule et même base de données, et ce, quel que soit son type.
Il est sous-entendu finalement qu'une autre application est censée être en place sur ces sites pour pouvoir interagir avec les données que les destinataires ou responsables de sites ont saisies.
Dans le contexte professionnel dans lequel je travaille, l'ensemble des sites possède une application itinérante qui est identique à l'application centrale où les utilisateurs, par le biais de processus d'export, déversent leurs données dans l'application centrale.
Mais pour ce faire, il est impératif que l'application itinérante soit pourvue des données en rapport avec les communes qu'ils exploitent d'une part, et les différents techniciens appartenant au site, d'autre part.
Lorsqu'ils font leur export, les données se croisent avec les informations récoltées.
J'ajoute à ce tutoriel une petite vidéo qui va vous permettre de vous rendre compte du mode de fonctionnement de cette application :
Démonstration du tutoriel en mode utilisation
Dans le jeu de tests présentés ci-dessus, j'ai expressément omis les informations pour afficher les messages d'erreur et pour arriver finalement avec le compte rendu final des étapes qualifiées d'incomplètes.
Je suis alors revenu au formulaire précédent jusqu'à l'étape incriminée et j'ai corrigé mon formulaire afin de n'avoir aucune erreur.
XVII. Conclusion▲
Bien que ce tutoriel reste axé sur un cas d'école très particulier, il reste intéressant d'en examiner le contenu, ne serait-ce que pour apprendre à éviter la redondance de code au sein des formulaires, surtout lorsque ceux-ci font la même chose.
Vous avez pu voir comment il était aisé de centraliser les procédures événementielles en un seul et même bloc et surtout comment il était plus souple de passer des paramètres pour pouvoir faire en sorte que ces procédures exécutent ce que vous souhaitez faire. Vous avez pu également apprendre à rendre optimiste une gestion d'erreurs, mais aussi à construire des classes formulaires génériques où seul le changement des valeurs de constantes suffit à faire marcher l'interface.
Pour pouvoir partager ce tutoriel et le transposer à l'image de ce que vous pourriez en attendre, j'ai dû apporter pas mal de modifications et adapter le code pour qu'il soit à la fois simplifié et compréhensible par rapport au projet original. Il n'en est pas moins que son élaboration reste assez délicate si l'on considère qu'il ne faut rien omettre, tant au sein des formulaires que de leurs propriétés.
Vous voudrez bien noter que toutes suggestions d'amélioration dans le but de parfaire ce projet restent bien évidemment les bienvenues.
XVIII. Remerciements▲
Je tiens à remercier tout particulièrement toutes celles et tous ceux qui ont participé à la relecture de ce document en y incluant leurs remarques.
- Claude Leloup pour la relecture orthographique.
- Djibril pour la partie technique de l'édition.











































