Réalisez un gestionnaire de modèles Word selon un jeu d'enregistrements

Jean-Philippe AMBROSINO

Commentez

- Titre : Réalisez un gestionnaire de modèles Word selon un jeu d'enregistrements

- Auteur : Jean-Philippe AMBROSINO

- Parution : 15 mai 2014

- Mise à jour : 3 juin 2014

- Licence : Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Avant-propos

- I-A. Niveau

- I-B. Contact

- I-C. Le projet proposé

- II. Pourquoi ce projet

- II-A. Contexte

- II-B. De quoi avez-vous besoin ?

- III. Présentation du projet

- III-A. Structure fonctionnelle du formulaire

- IV. Les tables du projet

- IV-A. La structure des tables

- IV-B. Données des tables

- IV-B-1. Exemple de données dans les tables

- V. Conception du formulaire

- V-A. Vue éclatée du formulaire

- V-B. Description des contrôles

- VI. Mode opératoire des événements pour les contrôles

- VI-A. Le bloc d'en-tête

- VI-B. Les procédures événementielles des contrôles

- VI-C. Les fonctions et procédures locales

- VII. Les modules externes

- VII-A. Le Module basFunctions

- VIII. Mise en exécution

- IX. Mise en pratique du projet

- X. Conclusion

- XI. Sujets corrélatifs

- XII. Remerciements

I - Avant-propos

Ce document a pour but de vous montrer comment concevoir un formulaire permettant d'établir un lien direct vers un modèle Word selon la valeur d'un enregistrement.

Au final, lorsque vous serez sur le point de lancer l'impression d'une fiche exploitant un modèle Word spécifique, le processus sélectionnera le fichier selon l'enregistrement qui a été utilisé pour le définir.

Vous devez être relativement à l'aise avec Microsoft Access et connaître la conception de formulaires, mais également la manipulation des requêtes avec Visual Basic, en ayant une bonne approche de ce langage afin de mettre en pratique cet exemple.

I-A - Niveau

Ce tutoriel s'adresse plus particulièrement aux personnes qui possèdent déjà des notions concernant la gestion des événements avec Visual Basic sur Microsoft Access incluant la manipulation des données à l'aide de DAO.

I-B - Contact

Pour tous renseignements complémentaires, veuillez me contacter directement (Argyronet) par MP.
Si votre question est, disons publique, merci de poster un message dans le forum afin d'en faire partager le contenu...

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 module.
Notez que j'ai cherché avant tout à simplifier l'ensemble pour que tout un chacun puisse l'assimiler de façon aisée.

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, vous devez être dans une situation où votre application Access exploite l'édition de documents via le traitement de texte Microsoft Word, d'une part et que ces éditions se fassent sur des modèles différents selon un certain contexte d'autre part.
À ce jour, la sélection du modèle pour vous est manuelle et automatiser le processus serait une chose intéressante.

Imaginons que vous ayez à éditer des rapports de comptes-rendus pour différentes collectivités et que chacune d'entre elles exige qu'il y ait son logo accouplé à quelques informations qui lui sont propres sur la page de garde du document...
Vous pouvez y répondre favorablement, mais vous être contraint de choisir manuellement le modèle prédéfini à chaque fois avant d'imprimer.
Si tel est le cas, ce tutoriel est fait pour vous.

Notez au passage que le code de ce projet a été modifié et adapté pour la circonstance afin que sa mise en place soit aisée et que sa présentation sous forme de formulaire autonome prêt à l'emploi le soit aussi.

II-B - De quoi avez-vous besoin ?

Pour mettre en œuvre le projet quasi complet de ce tutoriel, vous avez besoin :

- d'une version professionnelle de Microsoft Office ;

- d'un jeu de modèles Microsoft Word (2003-2013) ;

- que votre version d'Access soit identique à celle de Word ;

- d'un peu de temps pour réaliser le projet et modifier votre application pour l'intégrer.

Il est à considérer, et c'est sous-entendu, que les modèles sont pourvus de signets et que ces derniers sont alimentés avec la valeur des champs d'un Recordset par échange de données entre Microsoft Access et Microsoft Word via la technologie (OLE) Automation.

Note
Ce point ne sera abordé que succinctement dans ce tutoriel.

Remarque :dans ce tutoriel, j'ai considéré le cas d'école suivant :

- les modèles sont identifiés en fonction de communes. Dans notre exemple, je n'ai pris que les communes de l'Île de France, mais il est tout à fait envisageable d'y lister la totalité des communes de notre cher pays. Le procédé n'est guère plus long si vous n'êtes pas à quelques millisecondes près.

- l'ensemble est donc voué à proposer l'impression d'un document donné pour une commune qui lui est affectée. En d'autres termes, si votre application nécessite de générer des documents en fonction d'un critère cible comme justement une ville, c'est idéal pour vous. Sinon, il vous faudra procéder à quelques adaptations (noms des contrôles, des étiquettes et des messages affichés).

Ce tutoriel peut, dans son ensemble, être mis en application pour toutes les versions d'Access.
Il est présenté ici sous Access 2010 (14).

Toutefois, certaines méthodes seront inexistantes dans les versions antérieures à la version 2000 et des adaptations seront sans doute nécessaires.

III - Présentation du projet

Ce projet est un formulaire indépendant ouvert au moment voulu pour paramétrer les modèles Word.
On entend par paramétrer, le fait d'ajouter, supprimer ou modifier des modèles avec leurs clés cibles d'enregistrement qui permettront de les identifier pour les utiliser au moment de l'impression, qu'elle soit papier ou PDF.

L'appel de ce formulaire peut par exemple être effectué depuis la section "Préférences et paramètres" du menu principal de votre application.
Une fois ouvert, il se présente dépourvu de toute donnée (volontairement). S'il existe des modèles, la liste peut être consultée et éventuellement la faire évoluer. Sinon, un message vous avertit qu'aucun modèle n'a été défini.

Le formulaire fini

L'affichage présenté ci-dessus est le formulaire tel que chargé à l'ouverture.

Dans la table des modèles (voir §IV), il existe un modèle dont l'identifiant est 1 ; c'est le modèle par défaut.
En effet, lorsque vous lancerez une impression, pour peu qu'aucun modèle n'ait été affecté à la commune de la fiche à imprimer, il est nécessaire que le processus puisse choisir tout de même le modèle cible et donc, celui par défaut.

Ce bloc conditionnel sera à prendre en considération dans votre bloc de code qui gère les impressions.
En conséquence, vous devrez faire en sorte que ce modèle soit disponible d'une part et que son entrée dans la table comme enregistrement par défaut, soit effectuée manuellement.
Cette table ayant un compteur pour les identifiants des modèles, il faut que cette opération soit effectuée en premier ou bien que vous modifiiez l'interface en conséquence, car je n'ai pas prévu ce processus (version light du tutoriel).

III-A - Structure fonctionnelle du formulaire

La fenêtre est composée de différents contrôles dont certains sont masqués au démarrage, le chargement considérant que le mode ajout est le mode par défaut.
De ce fait, vous pouvez à l'ouverture, ajouter un nouveau modèle et l'enrichir des valeurs correspondant à son identification.

Pour ajouter un modèle, il vous faut le sélectionner via la boîte de dialogue Ouvrir qui est appelée avec la fameuse API GetOpenFileName(). Une fois le modèle sélectionné, le nom de ce dernier est greffé dans une zone de texte (Textbox) et l'ensemble des communes disponibles est listé dans la zone de liste (Listbox).

Il ne vous reste plus qu'à sélectionner les communes concernées une à une, la liste ayant sa propriété Multiselect définie en conséquence.Dès qu'une ou plusieurs communes sont sélectionnées, le bouton Valider est activé.
La validation stocke, après vérification, le modèle avec son chemin.
Une fois l'opération achevée, le formulaire repasse en mode saisie où il est possible d'ajouter un autre modèle avec le même procédé.

Vous pouvez alors ajouter un autre modèle ou consulter tous ceux que vous avez déjà stockés.

En mode consultation, vous accédez aux modèles existants depuis une liste déroulante ; celle-ci pilote la liste des communes lorsqu'un modèle est sélectionné et affiche alors les enregistrements correspondant au modèle.
Cette liste qui est en lecture seule ne peut faire l'objet d'aucune sélection. En cas de nécessité, il est donc possible de modifier le modèle pour ajuster les communes qui lui sont affectées, c'est à dire en supprimer ou en ajouter.

Lorsque le modèle est en mode modification, la liste de ses communes est remplacée par une autre liste qui propose alors toutes les communes disponibles à ceci près que celles affectées au modèle sont en surbrillance.

L'utilisateur peut sélectionner les communes en surbrillance pour les supprimer et sélectionner celles qui ne le sont pas pour les activer. Une validation sera alors nécessaire.
Notez au passage qu'un bouton permettant de supprimer l'ensemble des communes pour un modèle donné a été prévu.

IV - Les tables du projet

Il y a trois tables nécessaires à la mise en œuvre de ce projet :

Liste des tables du projet

- tblOutputReportTemplates : table des modèles Word.

- tblTemplatesAndTowns : table de liaison modèles / communes.

- tblTowns : table des communes.

Descriptif des tables

- La table des modèles Word contient les modèles que vous allez sélectionner.

- La table de liaison modèles / communes contient les identifiants de chacune des tables.

- La table des communes doit contenir toutes les communes qui sont souhaitées (ou tout autre type de valeur si votre idée pour exploiter ce tutoriel concerne un autre sujet).

IV-A - La structure des tables

La structure des tables reste des plus simples ; quelques champs au bas mot.

La table des modèles Word

Table tblOutputReportTemplates

Nom du champ

Type du champ

Taille du champ

TemplateID

Primaire Numéro Auto

4

TemplateName

Texte

64

TemplatePath

Texte

255

IsUsed

Oui/Non

1

La table de liaison modèles/communes

Table tblTemplatesAndTowns

Nom du champ

Type du champ

Taille du champ

TemplateID

Entier Long

4

IDTown

Entier Long

4

La table des communes

Table tblTowns

Nom du champ

Type du champ

Taille du champ

IDTown

Primaire Numéro Auto

4

PostalCode

Texte

5

TownName

Texte

100

Notez au passage que la notion de taille de champ pour les champs de type numérique ne peut pas être définie par le développeur.
Il ne faut donc pas essayer de trouver comment l'attribuer, celle-ci l'étant automatiquement.

IV-B - Données des tables

Rappel :

La table des modèles reçoit une valeur par défaut avec un ID attribué à 1.

Prenez en considération que ce chiffre 1 est initialisé par un compteur et doit donc être entré en premier.
C'est la seule entrée manuelle dans cette table.

J'aborderai ce point à la fin de cet article.

Le modèle par défaut entré manuellement dans la table se doit d'exister en tant que fichier physique.
Sur ce point, je vous fais bien entendu un clin d'œil pour que vous mettiez en place une gestion d'erreur traitant le cas N°53.

IV-B-1 - Exemple de données dans les tables

Les données affichées dans les illustrations ci-aprèsne sont représentatives que de mon projet.
Seuls les communes et le modèle par défaut ont été entrés manuellement dans leur table respective.

La table des modèles

La table de liaison modèles/communes

La table des communes

Une fois les tables conçues et alimentées comme il se doit, vous allez pouvoir vous attaquer à la conception du formulaire.

V - Conception du formulaire

Le formulaire est assez délicat à concevoir dans le sens où quelques contrôles se superposent, l'objectif ici étant de faire en sorte que selon le mode d'utilisation, il n'y ait pas de décalage visuel de pixel, notamment sur les cadres et les zones de liste accompagnés de leur étiquette.

C'est pourquoi je vous ai spécifié pour chacun d'eux, leur position et dimension si toutefois vous souhaitez reproduire à la lettre cet écran.

Pour mettre en œuvre ce tutoriel, vous allez donc ajouter dans un formulaire vierge un lot de contrôles qui sont listés ci-après :

- 13 contrôles Etiquette ;

- 2 contrôles Zone de texte ;

- 6 contrôles Rectangle ;

- 1 contrôle Groupe d'options ;

- 1 contrôle Zones de liste déroulante ;

- 2 contrôles Zone de liste ;

- 6 contrôles Bouton de commande.

Rappel
Chaque contrôle recevant ou affichant des données est systématiquement dessiné avec son étiquette ; de ce fait, vous n'avez pas à les ajouter.

Ces contrôles doivent tous être nommés (même les étiquettes - eh oui, c'est sans doute pénible, mais c'est plus pro, plus propre et en plus utile pour la lisibilité du code...).

V-A - Vue éclatée du formulaire

L'illustration ci-après montre le formulaire en vue éclatée pour que vous puissiez apprécier son agencement.

Vue éclatée

Dans une certaine mesure, je vous préconise :

1. De dessiner d'abord tous les contrôles en mode éclaté également ;

2. De leur affecter d'abord leur nom ;

3. De leur affecter ensuite leurs dimensions ;

4. De leur affecter (si nécessaire) leur couleur ;

5. De leur affecter les propriétés techniques comme l'activation ou la visibilité ;

6. Et enfin, de leur affecter leur position.

C'est plus commode et plus simple pour agencer des formulaires dont on possède les caractéristiques de leur contenu.

V-B - Description des contrôles

Vous allez donc dessiner les contrôles suivants.

Il est conseillé de dessiner les rectangles en dernier pour plus de commodité, notamment pour la sélection des autres contrôles devant être alignés sur les mêmes plans ou ceux devant être superposés.

Les zones de liste

Ces zones définissent respectivement la liste de toutes les communes et celle des communes déjà attribuées.

Liste des communes définies pour un modèle
Type = zone de liste Nom : lboTownsDefined
Position : Haut = 4,399 cm Gauche = 4,312 cm Largeur = 9,545 cm Hauteur = 4,813 cm

Liste des communes disponibles
Type = zone de liste Nom : lboTownToSet
Position : Haut = 4,392 cm Gauche = 18,254 cm Largeur = 9,545 cm Hauteur = 4,813 cm

La zone de liste déroulante

Cette liste permet de lister les modèles existants.

Liste des modèles
Type = zone de liste modifiable Nom : cboTemplateList
Position : Haut = 2,910 cm Gauche = 18,254 cm Largeur = 9,519 cm Hauteur = 0,584 cm

Les boutons de commande

Ces boutons sont dédiés aux actions de sélection, modification, suppression, consultation des modèles.

Bouton de validation du modèle ajouté ou modifié
Type = bouton de commande Nom : cmdValidate - Légende = &Valider ce modèle - Visible : OUI - Activé : NON
Position : Haut = 12,407 cm Gauche = 8,598 cm Largeur = 4,153 cm Hauteur = 0,700 cm

Bouton de fermeture (Fermer) ou de retour (Refermer)
Type = bouton de commande Nom : cmdClose - Légende = &Fermer ou &Refermer - Visible : OUI - Activé : OUI
Position : Haut = 12,407 cm Gauche = 12,751 cm Largeur = 2,354 cm Hauteur = 0,700 cm

Bouton de consultation des modèle ou leur modification
Type = bouton de commande Nom : cmdList - Légende = &Consulter la liste... ou &Modifier ce modèle... - Visible : OUI - Activé : NON
Position : Haut = 12,407 cm Gauche = 0,291 cm Largeur = 3,995 cm Hauteur = 0,700 cm

Bouton de sélection d'un fichier
Type = bouton de commande Nom : cmdOpenFile - Légende = ... - Visible : NON - Activé : OUI
Position : Haut = 2,884 cm Gauche = 13,280 cm Largeur = 0,593 cm Hauteur = 0,594 cm

Bouton de suppression des communes du modèle en cours
Type = bouton de commande Nom : cmdClearSelection - Légende = X - Visible : OUI - Activé : NON
Position : Haut = 9,286 cm Gauche = 13,175 cm Largeur = 0,698 cm Hauteur = 0,446 cm

Bouton de suppression du modèle actif
Type = bouton de commande Nom : cmdRemoveTemplate - Légende = &Supprimer ce modèle - Visible : OUI - Activé : NON
Position : Haut = 12,407 cm Gauche = 4,286 cm Largeur = 4,312 cm Hauteur = 0,700 cm

Les zones de texte

Une seule zone de texte reçoit une valeur, l'autre est dissimulée et sert au Focus.

Zone de texte dissimulée pour prendre le focus
Type = zone de texte Nom : txtVoid
Position : Haut = 0,000 cm Gauche = 0,000 cm Largeur = 0,000 cm Hauteur = 0,000 cm

Zone de texte définissant le modèle sélectionné
Type = zone de texte Nom : txtTemplateName
Position : Haut = 2,912 cm Gauche = 4,312 cm Largeur = 8,912 cm Hauteur = 0,582 cm

Le groupe d'options

Ce groupe sert à trier la liste des communes

Groupe de tri par commune ou code postal
(l'étiquette est stipulée plus bas lblSortBy)
Type = groupe d'options Nom : fraSortBy
Position : Haut = 6,164 cm Gauche = 1,402 cm Largeur = 2,702 cm Hauteur = 1,663 cm

Les boutons d'options
(les étiquettes sont stipulées plus bas lblByPostalCode et lblByTown)
Type = bouton d'option Nom : tgl1
Position : Haut = 6,584 cm Gauche = 1,730 cm Largeur = 0,459 cm Hauteur = 0,423 cm
Type = bouton d'options Nom : tgl2
Position : Haut = 7,166 cm Gauche = 1,730 cm Largeur = 0,459 cm Hauteur = 0,423 cm

Les étiquettes

Les étiquettes définissent les rôles des autres contrôles.

Étiquette des modèles existants
Type = étiquette Nom : lblTemplateList - Légende = Modèle &Word : - Visible : OUI - Couleur Texte : 0
Position : Haut = 2,917 cm Gauche = 15,212 cm Largeur = 2,899 cm Hauteur = 0,423 cm

Étiquette du groupe d'options
Type = étiquette Nom : lblSortBy - Légende = Trier par... - Visible : OUI - Couleur Texte : 0
Position : Haut = 5,952 cm Gauche = 1,614 cm Largeur = 1,534 cm Hauteur = 0,423 cm

Étiquette du bouton d'option tri par commune
Type = étiquette Nom : lblByTowns - Légende = Co&mmune - Visible : OUI - Couleur Texte : 0
Position : Haut = 6,534 cm Gauche = 2,136 cm Largeur = 1,402 cm Hauteur = 0,423 cm

Étiquette du groupe d'options tri par code postal
Type = étiquette Nom : lblByPostalCode - Légende = Co&de postal - Visible : OUI - Couleur Texte : 0
Position : Haut = 7,116 cm Gauche = 2,136 cm Largeur = 1,640 cm Hauteur = 0,423 cm

Étiquette masquée définissant le chemin du modèle (fait office de variable)
Type = étiquette Nom : lblTemplatePath* - Légende = #Path - Visible : NON - Couleur Texte : 255
Position : Haut = 1,984 cm Gauche = 12,090 cm Largeur = 1,194 cm Hauteur = 0,420 cm

Étiquette masquée définissant l'identifiant (ID) du modèle (fait office de variable)
Type = étiquette Nom : lblTempletaID* - Légende = #ID - Visible : NON - Couleur Texte : 255
Position : Haut = 1,984 cm Gauche = 13,360 cm Largeur = 0,873 cm Hauteur = 0,423 cm

Étiquette résumant le nombre de communes (disponibles, affectées...)
Type = étiquette Nom : lblTownsSelected - Légende = "" - Visible : OUI - Couleur Texte : 7633277
Position : Haut = 9,298 cm Gauche = 4,298 cm Largeur = 8,785 cm Hauteur = 0,446 cm

Étiquette de la zone de texte txtTemplateName
Type = étiquette Nom : lblTemplateName - Légende = Modèle &Word : - Visible : OUI - Couleur Texte : 0
Position : Haut = 2,919 cm Gauche = 1,270 cm Largeur = 2,899 cm Hauteur = 0,423 cm

Étiquette de la zone de liste lboTownToSet
Type = étiquette Nom : lblTownToSet - Légende = Communes associées : - Visible : OUI - Couleur Texte : 0
Position : Haut = 4,418 cm Gauche = 15,212 cm Largeur = 2,899 cm Hauteur = 0,476 cm

Étiquette de titre du formulaire (premier plan)
Type = étiquette Nom : lblTitleForm1* - Légende = Gestionnaire de modèles Word pour les communes - Visible : OUI - Couleur Texte : 8079968
Position : Haut = 0,397 cm Gauche = 0,000 cm Largeur = 15,450 cm Hauteur = 0,820 cm

Étiquette de titre du formulaire (arrière-plan)
Type = étiquette Nom : lblTitleForm2* - Légende = Gestionnaire de modèles Word pour les communes - Visible : OUI - Couleur Texte : 16764057
Position : Haut = 0,397 cm Gauche = 13,201 cm Largeur = 15,423 cm Hauteur = 0,820 cm

Étiquette de spécification de ce qu'il y a à faire
Type = étiquette Nom : lblWhatTodo* - Légende = "" - Visible : OUI - Couleur Texte : 16711680
Position : Haut = 1,984 cm Gauche = 1,085 cm Largeur = 10,926 cm Hauteur = 0,423 cm

Étiquette de la zone de liste lboTownsDefined
Type = étiquette Nom : lblTownsDefined - Légende = Communes associées : - Visible : OUI - Couleur Texte : 0
Position : Haut = 4,425 cm Gauche = 1,270 cm Largeur = 2,899 cm Hauteur = 0,476 cm

* Les étiquettes annotées d'un astérisque doivent être dessinées ; elles ne sont attachées à aucun contrôle

Les cadres de type Rectangle

Ces cadres permettent de déterminer des zones et de rendre le formulaire plus esthétique.

Cadre extérieur principal
Type = rectangle Nom : shpOutsideMain
Position : Haut = 1,534 cm Gauche = 0,661 cm Largeur = 14,051 cm Hauteur = 9,219 cm

Cadre intérieur principal (il entoure shpTemplate et shpTowns)
Type = rectangle Nom : shpInsideMain
Position : Haut = 1,693 cm Gauche = 0,794 cm Largeur = 13,734 cm Hauteur = 8,875 cm

Cadre du modèle en cours (il entoure txtTemplateName et cboTemplateList)
Type = rectangle Nom : shpTemplate
Position : Haut = 2,593 cm Gauche = 1,085 cm Largeur = 13,139 cm Hauteur = 1,205 cm

Cadre des boutons du bas
Type = rectangle Nom : shpButtons
Position : Haut = 11,190 cm Gauche = 0,185 cm Largeur = 15,055 cm Hauteur = 0,871 cm

Cadre de la liste des communes à définir(il entoure lboTownToSet)
Type = rectangle Nom : shpTowns
Position : Haut = 3,968 cm Gauche = 1,085 cm Largeur = 13,139 cm Hauteur = 6,257 cm

Cadre extérieur des modèles définis (il superpose shpOutsideMain)
Type = rectangle Nom : shpListOfTemplates
Position : Haut = 2,619 cm Gauche = 14,894 cm Largeur = 13,139 cm Hauteur = 7,633 cm

Les spécifications ci-dessus ne doivent être respectées que si vous souhaitez appliquer à la lettre la réalisation de ce formulaire. Vous pouvez bien entendu agencer ce dernier comme bon vous semble.
Notez toutefois qu'il est préférable de respecter les propriétés Caption, Visible et Enabled et surtout le nom des contrôles qui est en adéquation avec le code que je vais aborder plus loin.

Une fois le formulaire conçu et agencé comme il se doit, vous allez pourvoir vous attaquer au code événementiel qui permet son utilisation.

VI - Mode opératoire des événements pour les contrôles

Le module de classe du formulaire contient des procédures événementielles, des procédures et fonctions locales privées et aussi, des variables dites de module en en-tête.

VI-A - Le bloc d'en-tête

L'en-tête du module contient des constantes, des énumérations et des variables disponibles pour l'ensemble de la classe du formulaire.

- Les six constantes préfixées CMD définissent la légende des boutons (dynamiquement modifiées selon le contexte).

- Les trois constantes préfixées IS définissent le mot associé à la phrase inscrite dans le contrôle Label lblTownsSelected.

- L'énumération m_eSortBy, comme son nom l'indique, définit le sens su tri.

- L'énumération m_eUsingMode, comme son nom l'indique aussi, définit le mode d'utilisation en cours.

- Enfin, les trois variables m_blnIsNewTemplate, m_strSelectedTemplate et m_intUsingMode définissent respectivement le fait que vous êtes en train d'ajouter un nouveau modèle, le nom du modèle en cours et le mode d'utilisation en cours.

Déclaration

'**********************************************************************
' Project           : WordTemplateManager
' Module            : Form_frmTemplatesManager
' Type              : Document VBA
' DateTime          : 13/05/2014
' Author            : Jean-Philippe AMBROSINO
' Review            :
' Review date       :
' Purpose           : Module used to manage events and data from the form
'
'**********************************************************************
Option Compare Database
Option Explicit

Private Const CMD_SHOWLIST                             As String = "&Consulter la liste..."
Private Const CMD_VALIDATE_TEMPLATE                    As String = "&Valider ce modèle"
Private Const CMD_MODIFY_TEMPLATE                      As String = "&Modifier ce modèle"
Private Const CMD_RECLOSE                              As String = "&Refermer"
Private Const CMD_CLOSE                                As String = "&Fermer"
Private Const CMD_CANCEL                               As String = "&Annuler"

Private Const IS_AVAILABLE                             As String = "disponible"
Private Const IS_DEFINED                               As String = "affectée"
Private Const IS_SELECTED                              As String = "selectionnée"

Private Enum m_eSortBy
ByTown = 1
ByPostalCode = 2
End Enum

Private Enum m_eUsingMode
NotDefined = 0
UpdateTemplate = 1
ShowTemplateTowns = 2
CreateNewTemplate = 4
End Enum

Private m_blnIsNewTemplate                             As Boolean
Private m_strSelectedTemplate                          As String
Private m_intUsingMode                                 As m_eUsingMode

VI-B - Les procédures événementielles des contrôles

Pratiquement tous les contrôles se voient greffés d'un code événementiel.
Considérant que vous êtes censé n'avoir aucun modèle stocké (excepté le modèle par défaut, mais il n'est pas examiné), il est nécessaire d'afficher une information conséquente, traduite par un message régit dans l'événement Form_Load() :

Form_Load()

Private Sub Form_Load()
'Chargement du formulaire
'On rafraîchit alors l'affichage des contrôles
ShowHideControls False
'S'il n'existe aucun modèle...
If TemplateIsDefined(True, 0) = False Then
'...on affiche un message
MsgBox "Aucun modèle n'a été défini pour un jeu de communes donné." & vbCrLf & "Le modèle par défaut pour les éditions des rapports sera utilisé.", vbInformation, "Examen des modèles"
'et on désactive le bouton de consultation
cmdList.Enabled = False
'et on donne le focus au bouton Parcourir...
With cmdOpenFile
.Enabled = True
.SetFocus
End With
Else
'Sinon, le bouton de consultation est activé
cmdList.Enabled = True
End If
'On met à jour la légende au bas de la liste avec aucune valeur
Me.lblTownsSelected.Caption = ""
'On force le groupe à filtrer sur les communes et on le désactive
With Me.fraSortBy
.Value = ByTown
.Enabled = False
End With
'La variable de nouveau modèle est réinitialisée
m_blnIsNewTemplate = False
End Sub

La première chose que l'utilisateur aura sans doute envie de faire sera de consulter la liste, si liste il y a.
Le bouton cmdList portant deux légendes possibles, le bloc événementiel cmdList_Click est assez conséquent.

L'identification de ce qui va être exécuté lorsque l'on clique dessus est gérée par un Select Case basé sur sa légende.

cmdList_Click

Private Sub cmdList_Click()
'Bouton Consulter/Modifier permettant d'afficher les modèles stockés ou de modifier un modèle
Dim oLboDefined                                        As ListBox
Dim oLboListToSet                                      As ListBox

Dim lngTemplateID                                      As Long
Dim SQLSelect                                          As String
Dim lngCountTowns                                      As Long
Dim I                                                  As Long
Dim J                                                  As Long
Dim T                                                  As Long
Dim lngaIDTown()                                       As Long
Dim lngIDTown                                          As Long

'Vérifie la légende du bouton Liste/Modifier
Select Case cmdList.Caption
Case CMD_SHOWLIST
'Si l'on est en mode consultation
'On change la variable de module avec le flag correspondant
m_intUsingMode = ShowTemplateTowns
'On construit une clause SQL permettant de lister tous les modèles exceptés celui par défaut
SQLSelect = "SELECT T.TemplateID, T.TemplateName "
SQLSelect = SQLSelect & "FROM tblTemplatesAndTowns AS TT RIGHT JOIN tblOutputReportTemplates AS T ON TT.TemplateID = T.TemplateID "
SQLSelect = SQLSelect & "GROUP BY T.TemplateID, T.TemplateName "
SQLSelect = SQLSelect & "HAVING T.TemplateID > 1 "
SQLSelect = SQLSelect & "ORDER BY T.TemplateID;"
'On affecte le contrôle Listbox cible
With Me.cboTemplateList
.RowSource = SQLSelect
.Value = vbNullString
End With
'On met à jour la légende au bas de la liste avec aucune valeur
Me.lboTownsDefined.RowSource = vbNullString
'On gère l'affichage des contrôles
ShowHideControls True

Case CMD_MODIFY_TEMPLATE
'Si l'on est en mode modification
'On change la variable de module avec le flag correspondant
m_intUsingMode = UpdateTemplate
'On récupère l'ID du modèle
lngTemplateID = Nz(Me.cboTemplateList, 0)
'S'il y a en a un, on établit la liste des communes selon le tri en cours
If lngTemplateID Then
If Me.fraSortBy = ByTown Then
SQLSelect = "SELECT C.IDTown, TownName & ' (' & PostalCode & ')' AS City "
SQLSelect = SQLSelect & "FROM tblTowns AS C "
SQLSelect = SQLSelect & "ORDER BY C.TownName;"
Else
SQLSelect = "SELECT C.IDTown, PostalCode & ' (' & TownName & ')' AS CP "
SQLSelect = SQLSelect & "FROM tblTowns AS C "
SQLSelect = SQLSelect & "ORDER BY C.PostalCode;"
End If
'On affecte le contrôle ComboBox cible
Me.lboTownToSet.RowSource = SQLSelect
'On appose le nom du modèle sélectionné à la zone de texte
Me.txtTemplateName = Me.cboTemplateList.Column(1)
'On efface le tableau des communes
Erase lngaIDTown()
'On affecte les variables objet à chacune des Listbox
Set oLboDefined = Me.lboTownsDefined
Set oLboListToSet = Me.lboTownToSet
'On compte le nombre d'éléments
lngCountTowns = oLboDefined.ListCount
'Pour chaque commune appartenant à la liste des communes affectées au modèle,
'on alimente le tableau de type Long : lngaIDTown()
For I = 0 To lngCountTowns - 1
ReDim Preserve lngaIDTown(0 To I)
lngaIDTown(I) = oLboDefined.ItemData(I)
Next I
'S'il y a des communes affectées (il peut ne pas y en avoir)
If lngCountTowns Then
'Alors on compte le nombre de communes total
lngCountTowns = oLboListToSet.ListCount
'Et pour chacune de ces communes dans la liste des communes à définir
For I = 0 To lngCountTowns - 1
With oLboListToSet
'On récupère l'identifiant
lngIDTown = .ItemData(I)
'Et pour chaque élément du tableau (représentant chaque commune affectée au modèle)
For J = LBound(lngaIDTown) To UBound(lngaIDTown)
'Si elle correspond...
If lngaIDTown(J) = lngIDTown Then
'Alors on la met en surbrillance
.Selected(I) = True
T = T + 1
End If
Next J
End With
Next I
End If
'On gère alors l'affichage des contrôles
ShowHideControls False
'On met à jour la légende au bas de la liste avec le nombre de communes sélectionnées
RefreshNumberOfTowns oLboListToSet, IS_SELECTED, False, lngCountTowns
'On change l'état activé du bouton d'effacement des communes selon le cas
cmdClearSelection.Enabled = (T > 0)
Else
MsgBox "Veuillez sélectionner le modèle à modifier...", vbExclamation, "Aucun modèle"
End If
End Select
'On libère les varibel objet
Set oLboDefined = Nothing
Set oLboListToSet = Nothing
End Sub

Dans la mesure où il n'y a rien à consulter, l'utilisateur va donc ajouter un modèle.
C'est le bouton cmdOpenFile qui s'en charge en affichant la boîte de dialogue Ouvrir.
Si un modèle est effectivement sélectionné, la procédure affiche toutes les communes disponibles, sinon, un message est affiché.

cmdOpenFile_Click

Private Sub cmdOpenFile_Click()
'Bouton [...] permettant de sélectionner un modèle Word
Const WORD_FILTER                                      As String = "Modèles Word 2007 (*.dotx)" & vbNullChar & "*.dotx" & vbNullChar & "Modèles Word 2007 avec macros (*.dotm)" & vbNullChar & "*.dotm" & vbNullChar & "Modèles Word 97-2003(*.dot)" & vbNullChar & "*.dot"
Dim strTemplateName                                    As String
Dim strTemplatePath                                    As String
Dim SQLSelect                                          As String

On Error GoTo L_ErrcmdOpenFile_Click
With Application
'On appelle la fonction de sélection du fichier
If OpenWordTemplateFile(Me.HWnd, .hWndAccessApp, "Sélection d'un modèle", .CurrentProject.Path, WORD_FILTER, strTemplateName) = False Then
Err.Raise 18, "Sélection annulée", "Vous n'avez pas sélectionné de modèle à ajouter à votre liste."
End If
End With
If Len(strTemplateName) = 0 Then
'Si aucun fichier n'a été sélectionné
MsgBox "Vous devez sélectionner un document Word pour enrichir la liste des correspondances Modèle/Communes.", vbExclamation, "Sélection non valide"
Me.txtTemplateName = ""
Else
'Si un fichier a bien été sélectionné...
'On récupère son chemin
strTemplatePath = GetPathnameFromFullPath(strTemplateName, False)
'On l'affecte au contrôle Label caché (pour le fun ; on aurait pu le mettre dans une variable)
Me.lblTemplatePath.Caption = strTemplatePath
'On affecte la zone de texte avec le nom du fichier
Me.txtTemplateName = GetFilenameFromFullPath(strTemplateName)
'On vérifie que le modèle n'a pas déjà été défini
If IsTemplateAlreadyDefined(Me.txtTemplateName, Me.lblTemplatePath.Caption) Then
'Si oui, alors on désélectionne tous les items
UnselectAllItem lboTownToSet
'On lève l'erreur
err.Raise 3022, "Sélection incorrecte", "Le modèle '" & Me.txtTemplateName & "' que vous avez sélectionné a déjà été défini pour ce même dossier."
Else
'Sinon, selon l'option de tri...
With Me.fraSortBy
If .Value = ByTown Then
SQLSelect = "SELECT C.IDTown, TownName & ' (' & PostalCode & ')' AS Town "
SQLSelect = SQLSelect & "FROM tblTowns AS C "
SQLSelect = SQLSelect & "ORDER BY C.TownName;"
Else
SQLSelect = "SELECT C.IDTown, PostalCode & ' (' & TownName & ')' AS CP "
SQLSelect = SQLSelect & "FROM tblTowns AS C "
SQLSelect = SQLSelect & "ORDER BY C.PostalCode;"
End If
.Enabled = True
End With
'... on rempli la liste avec toutes les communes
Me.lboTownToSet.RowSource = SQLSelect
'On met à jour la légende au bas de la liste avec le nombre de communes disponibles
RefreshNumberOfTowns lboTownToSet, IS_AVAILABLE, False
'Et on modifie la valeur des variables en spécifiant que l'on ajoute un nouveau modèle
m_blnIsNewTemplate = True
m_intUsingMode = CreateNewTemplate
End If
End If
On Error GoTo 0
L_ExcmdOpenFile_Click:
Exit Sub

L_ErrcmdOpenFile_Click:
MsgBox Err.Description, 48, Err.Source
Resume L_ExcmdOpenFile_Click
End Sub

À tout instant, l'utilisateur peut fermer le formulaire sans avertissement (cela n'a pas été prévu ici).

Deux cas de figure existent pour les boutons de fermeture selon la légende qu'il porte.

cmdClose_Click

Private Sub cmdClose_Click()
'Selon la légende du bouton Fermer
Select Case cmdClose.Caption
Case CMD_CLOSE
'Si c'est Fermer, on ferme le formulaire
DoCmd.Close acForm, Me.Name, acSaveYes
Case CMD_RECLOSE
'Si c'est Refermer, on gère l'affichage des contrôles
ShowHideControls False
End Select
End Sub

Lorsque l'on est en mode consultation, la liste déroulante contient les modèles existants.

Dès la sélection d'un modèle depuis cette liste, l'événement AfterUpdate() est déclenché :

cboTemplateList_AfterUpdate

Private Sub cboTemplateList_AfterUpdate()
'Zone de liste modifiable des modèles Word

'Une fois le modèle sélectionné, on affiche les communes qui lui sont affectées
RefreshTownList Me.cboTemplateList, False
'On stocke l'ID du modèle dans un contrôle Label (pour le fun ; on aurait pu le mettre dans une variable)
Me.lblTempletaID.Caption = Me.cboTemplateList
'On change la légende du bouton Liste
With cmdList
.Caption = CMD_MODIFY_TEMPLATE
.Enabled = True
End With
'On change l'état des boutons Valider et Supprimer
cmdValidate.Enabled = False
cmdRemoveTemplate.Enabled = True
'Et celui du groupe d'options
fraSortBy.Enabled = True
'On vérifie que le modèle défini possède des communes
If TemplateIsDefined(False, Me.cboTemplateList) = False Then
Me.lblTownsSelected.Caption = "Aucune commune n'a été définie pour ce modèle"
End If
End Sub

Dès qu'un modèle est affiché avec (ou sans) ses communes, l'utilisateur peut très bien souhaiter le supprimer.

Cette action est confirmée par un message.

cmdRemoveTemplate_Click

Private Sub cmdRemoveTemplate_Click()
'Bouton Supprimer autorisant la suppression d'un modèle
Dim oLboDefined                                        As ListBox
Dim SQLDelete                                          As String
Dim lngTemplateID                                      As Long

On Error GoTo L_ErrcmdRemoveTemplate_Click
'Message de confirmation
If MsgBox("Voulez-vous supprimer le modèle '" & Me.cboTemplateList.Column(1) & "' de l'application  ?", vbQuestion + vbYesNo + vbDefaultButton2, "Supprimer le modèle") = vbYes Then
'Si l'on confirme
Set oLboDefined = Me.lboTownsDefined
'On récupère l'ID du modèle
lngTemplateID = Nz(Me.cboTemplateList, 0)
'S'il y en a un (ComboBox non Null)
If lngTemplateID Then
'On supprime le modèle
SQLDelete = "DELETE * FROM tblOutputReportTemplates WHERE TemplateID =" & lngTemplateID & ";"
CurrentDb.Execute SQLDelete, dbFailOnError
'Puis on supprime sa définition de communes
SQLDelete = "DELETE * FROM tblTemplatesAndTowns WHERE TemplateID =" & lngTemplateID & ";"
CurrentDb.Execute SQLDelete, dbFailOnError
Else
MsgBox "Veuillez sélectionner le modèle à supprimer...", vbExclamation, "Aucun modèle"
End If
'On rafraîchit la liste des modèles
Me.cboTemplateList.Requery
'La liste des communes est alors vide
oLboDefined.RowSource = vbNullString
'On met à jour la légende au bas de la liste avec aucune valeur
Me.lblTownsSelected.Caption = vbNullString
'On change l'état des boutons Consulter et Supprimer
Me.cmdList.Enabled = False
Me.cmdRemoveTemplate.Enabled = False
'Et celui du groupe d'options
Me.fraSortBy.Enabled = False
End If

On Error GoTo 0
L_ExcmdRemoveTemplate_Click:
Exit Sub

L_ErrcmdRemoveTemplate_Click:
MsgBox Err.Description, 48, Err.Source
Resume L_ExcmdRemoveTemplate_Click
End Sub

Dans le même esprit, dès qu'un modèle est affiché avec ses communes, l'utilisateur peut très bien souhaiter effacer toutes ses communes pour lui en affecter d'autres lorsqu'il est en mode consultation, alors qu'en mode ajout ou en mode modification, il sélectionne ou désélectionne les communes à affecter au modèle Word en cours.

Quelle que soit l'opération, la validation est obligatoire pour prendre en compte sa demande.
Elle est régie par le biais du bouton cmdValidate et son événement Click dans lequel l'opération à effectuer est supervisée selon le mode d'utilisation en cours déterminé par la variable m_intUsingMode.

cmdValidate_Click

Private Sub cmdValidate_Click()
'Bouton de validation du modèle
Dim oDB                                                As DAO.Database
Dim oRS                                                As DAO.Recordset
Dim oLboTownToSet                                      As ListBox
Dim lngTemplateID                                      As Long
Dim SQLSelect                                          As String
Dim SQLInsert                                          As String
Dim SQLDelete                                          As String
Dim lngCountTowns                                      As Long
Dim I                                                  As Long
Dim T                                                  As Long
Dim lngaIDTown()                                       As Long
Dim lngIDTown                                          As Long

On Error GoTo L_ErrcmdValidate_Click
'Sablier...
DoCmd.Hourglass True
'Si ce n'est pas un nouveau modèle, on affecte l'ID à la variable
If Not m_blnIsNewTemplate Then
lngTemplateID = Me.lblTempletaID.Caption
End If
'S'il n'y a pas d'ID et que ce n'est pas un nouveau modèle
If lngTemplateID = 0 And m_blnIsNewTemplate = False Then
'On lève une erreur
err.Raise 17, "Aucun identifiant définissant le modèle n'a été défini..." & vbCrLf & vbCrLf & "Veuillez recommencer l'opération."
End If
'On vide le tableau et on instancie la BDD
Erase lngaIDTown()
Set oDB = CurrentDb
'Si l'on est en mode Consultation...
If m_intUsingMode = ShowTemplateTowns Then
With oDB
'On supprime toutes les communes du modèle (l'utilisateur à cliqué sur le bouton X)
SQLDelete = "DELETE * FROM tblTemplatesAndTowns WHERE TemplateID = " & lngTemplateID & ";"
.Execute SQLDelete, dbFailOnError
End With
'On prévient que le modèle n'a plus de commune et on propose d'en affecter d'autres...
If MsgBox("Le modèle ne possède plus de communes associées." & vbCrLf & vbCrLf & "Voulez-vous lui affecter de nouvelles communes ?", vbQuestion + vbYesNo, "") = vbYes Then
'On change le statut des variables
m_blnIsNewTemplate = True
m_intUsingMode = UpdateTemplate
'Puis on rafraîchit la liste avec les communes selon les paramètres de la procédure
RefreshTownList Me.cboTemplateList, True
Else
'On change le statut des variables à NonDefini
m_intUsingMode = NotDefined
'Et on modifie l'affichage des contrôles du formulaire
ShowHideControls False
End If
Else
'Si l'on est dans un autre mode que Consultation...
'On affecte la variable de la liste des communes à affecter
Set oLboTownToSet = Me.lboTownToSet
'Compteur du tableau
T = -1
'On remplit le tableau avec les communes sélectionnées
With oLboTownToSet
lngCountTowns = .ListCount
If lngCountTowns Then
For I = 0 To lngCountTowns - 1
If .Selected(I) Then
T = T + 1
ReDim Preserve lngaIDTown(T)
lngaIDTown(T) = .ItemData(I)
End If
Next I
'Si aucune commune n'a été sélectionnée, on lève une erreur
If T = 0 Then
Err.Raise 17, "Aucune commune sélectionnée", "Vous devez sélectionner les communes à affecter pour ce modèle !"
End If
'Sinon, on démarre l'ajout du modèle
With oDB
If m_blnIsNewTemplate Then
'Si c'est un nouveau modèle... on l'ajoute et on récupère son ID
SQLSelect = "SELECT TemplateID, TemplateName, TemplatePath, IsUsed FROM tblOutputReportTemplates;"
Set oRS = .OpenRecordset(SQLSelect, 2)
With oRS
.AddNew
lngTemplateID = .Fields("TemplateID").Value
.Fields("TemplateName").Value = Me.txtTemplateName
.Fields("TemplatePath").Value = Me.lblTemplatePath.Caption
.Fields("IsUsed").Value = True
.Update
.Close
End With
Else
'Sinon, on efface d'abord les communes déjà affectées à ce modèle
If m_blnIsNewTemplate = False Then
SQLDelete = "DELETE * FROM tblTemplatesAndTowns WHERE TemplateID = " & lngTemplateID & ";"
.Execute SQLDelete, dbFailOnError
End If
End If
'Puis on les ajoute une par une selon les éléments du tableau
For I = 0 To T
SQLInsert = "INSERT INTO tblTemplatesAndTowns (TemplateID, IDTown) VALUES (" & lngTemplateID & "," & lngaIDTown(I) & ");"
.Execute SQLInsert, dbConsistent
Next
End With
Else
'Si aucune commune n'existe pour ce modèle,on lève une erreur
err.Raise 17, "Aucune commune affectée à ce modèle n'a été définie..." & vbCrLf & vbCrLf & "Veuillez sélectionner des communes."
End If
End With
'On rafraîchit alors l'affichage des contrôles
ShowHideControls False
'On met à jour la légende au bas de la liste avec aucune valeur
Me.lblTownsSelected.Caption = vbNullString
'On change l'état du bouton d'effacement (des communes)
Me.cmdClearSelection.Enabled = (T > 0)
'On vide la zone de texte du nom du modèle
Me.txtTemplateName.Value = vbNullString
'On vide la liste
Me.lboTownToSet.RowSource = vbNullString
'On désactive les options de tri
Me.fraSortBy.Enabled = False
'On réinitialise la variable stipulant un nouveau modèle
m_blnIsNewTemplate = False
End If

On Error GoTo 0
L_ExcmdValidate_Click:
'Sablier en pointeur
DoCmd.Hourglass False
'Libération des objets
Set oLboTownToSet = Nothing
Exit Sub

L_ErrcmdValidate_Click:
MsgBox Err.Description, vbExclamation, Err.Source
Resume L_ExcmdValidate_Click
End Sub

En mode consultation comme en mode ajout, le tri sur les communes est possible pour une consultation plus aisée selon le cas.

Le groupe d'options est chargé de faire cette opération en appelant la procédure privée RefreshTownList.

fraSortBy_AfterUpdate

Private Sub fraSortBy_AfterUpdate()
'Tri des éléments (ici communes)
RefreshTownList Nz(Me.cboTemplateList, 0), False
End Sub

Lorsque l'utilisateur sélectionne ou désélectionne une commune, la légende au-dessous des listes est rafraîchie en conséquence.

Cette procédure vérifie en parallèle que la commune sélectionnée ne l'a pas déjà été pour un autre modèle sans autres options.

Si vous souhaitez développer les répercutions de ce comportement, il est possible :

- d'empêcher/confirmer la sélection de cette commune (c'est le cas ici avec un message à confirmer);

- de ne lister que les communes qui n'ont pas déjà été sélectionnées ;

- de prévenir de choisir le modèle au moment de l'impression.

lboTownToSet_AfterUpdate

Private Sub lboTownToSet_AfterUpdate()
'Rafraîchissement des informations sur le nombre de communes définies et confirmation des nouvelles sélections
Dim lngCountTowns                                      As Long
Dim oLboListToSet                                      As ListBox
Dim lngItemSelected                                    As Long
Dim strItemSeleted                                     As String

Set oLboListToSet = Me.lboTownToSet
With oLboListToSet
lngItemSelected = .ListIndex
If .Selected(lngItemSelected) Then
lngCountTowns = TownIsAlreadyAttached(.Column(0))
strItemSeleted = .Column(1)
If lngCountTowns Then
If MsgBox("La commune " & strItemSeleted & " a déjà été définie pour " & PluralSingular("un autre modèle", lngCountTowns) & "." & vbCrLf & vbCrLf & "Voulez-vous toujours séléctionner cette commune ?", vbExclamation + vbYesNo, "Attention") = vbNo Then
.Selected(lngItemSelected) = False
End If
End If
End If
RefreshNumberOfTowns oLboListToSet, IS_DEFINED, True
Set oLboListToSet = Nothing
End With
End Sub

Lorsque l'utilisateur souhaite supprimer toutes les communes d'un modèle pour lui en affecter d'autres, par exemple, il clique sur le bouton (pourvu d'une sorte de croix) situé sous la liste.

Cette opération n'est disponible qu'en mode consultation ; elle est à confirmer par un message :

cmdClearSelection_Click

Private Sub cmdClearSelection_Click()
Dim strMessage                                         As String
Dim C                                                  As Long

'Donne le focus à un champ invisible
Me.txtVoid.SetFocus
'Vérifie la légende du bouton Liste/Modifier
Select Case cmdList.Caption
Case CMD_SHOWLIST
'Si l'on est en mode consultation
strMessage = "Voulez-vous supprimer toutes les communes affectées au modèle" & vbCrLf & Me.cboTemplateList.Column(1) & vbCrLf & vbCrLf & "Vous devrez valider pour que cette action soit prise en compte."
If MsgBox(strMessage, vbExclamation + vbYesNo + vbDefaultButton2, "Confirmez l'action") = vbYes Then
'On désélectionne tous les éléments de la liste (ici les communes)
For C = 0 To lboTownToSet.ListCount - 1
Me.lboTownToSet.Selected(C) = False
Next
'On met à jour la légende au bas de la liste
RefreshNumberOfTowns lboTownToSet, IS_AVAILABLE, False
'On désactive le bouton Valider
Me.cmdValidate.Enabled = False
End If
Case CMD_MODIFY_TEMPLATE
'Si l'on est en mode modification
strMessage = "Voulez-vous effacer toutes les communes sélectionnées pour le modèle" & vbCrLf & Me.cboTemplateList.Column(1) & " ?"
If MsgBox(strMessage, vbExclamation + vbYesNo + vbDefaultButton2, "Effacer la liste") = vbYes Then
'On active le bouton Valider en lui donnant le focus
With Me.cmdValidate
.Enabled = True
.SetFocus
End With
'On définit la propriété Contenu à NullString
Me.lboTownsDefined.RowSource = vbNullString
'On désactive les boutons Liste, Supprimer et le groupe d'options de tri.
Me.cmdList.Enabled = False
Me.cmdRemoveTemplate.Enabled = False
Me.fraSortBy.Enabled = False
End If
End Select
End Sub

VI-C - Les fonctions et procédures locales

Il y a deux fonctions et quatre procédures locales dans la classe de ce formulaire.

Avant de pouvoir ajouter un modèle, il est nécessaire de contrôler s'il n'a pas déjà été créé.
En parallèle à cela, il existe aussi une vérification au chargement qui contrôle la présence de modèles (cas de première utilisation), mais également, lorsqu'un modèle existe, qu'il possède des communes.

Tout cela est supervisé par la fonction TemplateIsDefined() ci-après.
Dans cette fonction, les arguments Init et TemplateId servent respectivement :
- à la vérification des modèles au chargement du formulaire pour le premier ;
- et pour le second, si TemplateId possède une valeur, on considère alors qu'il s'agit de la vérification des communes d'un modèle donné.

TemplateIsDefined

Private Function TemplateIsDefined(ByVal Init As Boolean, ByVal TemplateId As Long) As Boolean
'Fonction permettant de savoir si un modèle existe ou qu'il possède des communes

If Init Then
TemplateIsDefined = (DCount("TemplateID", "tblOutputReportTemplates") > 0)
Else
If TemplateId Then
TemplateIsDefined = (DCount("TemplateID", "tblTemplatesAndTowns", "TemplateID=" & TemplateId) > 0)
Else
TemplateIsDefined = (DCount("TemplateID", "tblTemplatesAndTowns") > 0)
End If
End If
End Function

À l'instar de la fonction ci-avant, la fonction TownIsAlreadyAttached permet de déterminer si une commune a déjà été attachée à un modèle auquel cas, un avertissement est affiché.

TownIsAlreadyAttached

Private Function TownIsAlreadyAttached(ByVal TownId As Long) As Long
'Fonction permettant de savoir si une commune existe pour plusieurs modèles
If TownId Then
TownIsAlreadyAttached = DCount("TemplateID", "tblTemplatesAndTowns", "IDTown=" & TownId)
End If
End Function

Afin de rendre plus souple l'affichage et le masquage de certains contrôles ou de définir leur état activé ou non, la procédure ShowHideControls est mise en place.

Elle est appelée un grand nombre de fois dans presque tous les événements.

ShowHideControls

Private Sub ShowHideControls(ByVal ShowIt As Boolean)
'Procédure de rafraîchissement des contrôles
Me.txtVoid.SetFocus
Me.shpTemplate.Visible = Not ShowIt
Me.txtTemplateName.Visible = Not ShowIt
Me.cmdOpenFile.Visible = Not ShowIt
Me.shpTowns.Visible = Not ShowIt
Me.lboTownToSet.Visible = Not ShowIt

Me.shpListOfTemplates.Visible = ShowIt
Me.cboTemplateList.Visible = ShowIt
Me.lboTownsDefined.Visible = ShowIt

Me.cmdClearSelection.Visible = IIf(m_intUsingMode = NotDefined, ShowIt, Not ShowIt)

Me.lblTownsSelected.Caption = vbNullString

If ShowIt Then
Me.lblWhatTodo.Caption = "Veuillez sélectionner le modèle Word pour connaître les communes associées :"
Else
Me.lblWhatTodo.Caption = "Veuillez sélectionner un modèle Word (*.dot) puis ses communes associées :"
End If

With cmdList
.Caption = IIf(ShowIt, CMD_MODIFY_TEMPLATE, CMD_SHOWLIST)
.Enabled = Not ShowIt
End With
Me.fraSortBy.Enabled = IIf(m_intUsingMode = UpdateTemplate, True, ShowIt)
cmdRemoveTemplate.Enabled = False
cmdValidate.Enabled = False
cmdClose.Caption = IIf(ShowIt, CMD_RECLOSE, CMD_CLOSE)
End Sub

Pour tenir informé l'utilisateur des communes affectées, disponibles ou sélectionnées pour un modèle en cours, la procédure RefreshNumberOfTowns() est appelée.

RefreshNumberOfTowns

Private Sub RefreshNumberOfTowns(ByVal TargetList As ListBox, ByVal TownStatus As String, ByVal ShowList As Boolean, Optional ByVal TotalCount As Long)
'Procédure de rafraîchissement des contrôles selon la liste passée en paramètre et le statut en cours
Dim T                                                  As Long
Dim lngCount                                           As Long
'On donne le focus à un contrôle non visible
Me.txtVoid.SetFocus
'On énumère les communes sélectionnées
With TargetList
For T = 0 To TargetList.ListCount - 1
If .Selected(T) Then
lngCount = lngCount + 1
End If
Next
End With
'Selon le nombre, le bouton Valider est activé ou non
Me.cmdValidate.Enabled = (lngCount > 0)
If lngCount = 0 Then
lngCount = Me.lboTownsDefined.ListCount
End If
'On met à jour la légende au bas de la liste en accordant en genre et en nombre
Me.lblTownsSelected.Caption = lngCount & PluralSingular("commune " & TownStatus, lngCount) & IIf(TotalCount, "sur" & TotalCount & PluralSingular(" est affectée ", TotalCount) & "à ce modèle", " pour ce modèle")
'On affecte l'état du bouton de suppression des communes
With Me.cmdClearSelection
.Enabled = (lngCount > 0)
.Visible = ShowList
End With
End Sub

L'élément prépondérant du formulaire est avant tout l'affichage des communes d'un modèle donné.

Le procédure RefreshTownList se charge d'alimenter l'une ou l'autre liste selon le contexte.

En effet, il existe une liste chargée d'afficher toutes les communes lorsque l'utilisateur ajoute un modèle.
Cette même liste se remplit également avec ces communes, à la différence que certaines d'entre elles seront mises en surbrillance si l'on souhaite modifier un modèle.

Et puis il existe une autre liste, verrouillée cette fois, qui ne sert qu'à afficher les communes affectées à un modèle choisi.

En gros, le principe est le suivant :
s'il y a un TemplateId passé en paramètre, la procédure va ouvrir une requête avec la condition WHERE correspondante ;pour cette condition, il faut que le paramètre ResetListOfTowns soit à False.
Sinon, la liste est remplie avec toutes les communes selon le tri du groupe d'options.

RefreshTownList

Private Sub RefreshTownList(ByVal TemplateId As Long, ByVal ResetListOfTowns As Boolean)
'Procédure dédiées à rafraîchir la liste des communes
Dim SQLSelect                                          As String
Dim SQLWhere                                           As String

'S'il y a un ID et que l'on a pas effacé les communes
If TemplateId <> 0 And ResetListOfTowns = False Then
'On rempli la liste avec les communes du modèle selon le tri
If Me.fraSortBy = ByTown Then
SQLSelect = "SELECT C.IDTown, C.TownName & ' (' & C.PostalCode & ')' AS Town "
SQLSelect = SQLSelect & "FROM tblTemplatesAndTowns AS TT LEFT JOIN tblTowns AS C ON TT.IDTown = C.IDTown "
SQLSelect = SQLSelect & "WHERE TT.TemplateID=" & TemplateId & " "
SQLSelect = SQLSelect & "GROUP BY C.IDTown, C.TownName & ' (' & C.PostalCode & ')', C.TownName "
SQLSelect = SQLSelect & "ORDER BY C.TownName;"
Else
SQLSelect = "SELECT C.IDTown, C.PostalCode & ' (' & C.TownName & ')' AS CP "
SQLSelect = SQLSelect & "FROM tblTemplatesAndTowns AS TT LEFT JOIN tblTowns AS C ON TT.IDTown = C.IDTown "
SQLSelect = SQLSelect & "WHERE TT.TemplateID=" & TemplateId & " "
SQLSelect = SQLSelect & "GROUP BY C.IDTown, C.PostalCode & ' (' & C.TownName & ')', C.PostalCode "
SQLSelect = SQLSelect & "ORDER BY C.PostalCode;"
End If
Else
'Sinon, on rempli avec toutes les communes selon le tri
If Me.fraSortBy = ByTown Then
SQLSelect = "SELECT IDTown, TownName & ' (' & PostalCode & ')' AS Town "
SQLSelect = SQLSelect & "FROM tblTowns "
SQLSelect = SQLSelect & "ORDER BY TownName;"
Else
SQLSelect = "SELECT IDTown, PostalCode & ' (' & TownName & ')' AS CP "
SQLSelect = SQLSelect & "FROM tblTowns "
SQLSelect = SQLSelect & "ORDER BY PostalCode;"
End If
End If
'Si l'on est en mode consultation
If m_intUsingMode = ShowTemplateTowns Then
'On rempli la liste des communes déjà définies
Me.lboTownsDefined.RowSource = SQLSelect
Else
'Sinon, on rempli la liste des communes à définir ou compléter
Me.lboTownToSet.RowSource = SQLSelect
'Si ce n'est pas un nouveau modèle...
If Not m_blnIsNewTemplate Then
'...alors, on rafraîchit l'affichage des contrôles
ShowHideControls True
End If
End If
'Et selon que ce sont des communes définies ou disponibles (à affecter), on change la légende au bas de la liste
RefreshNumberOfTowns lboTownsDefined, IIf(m_blnIsNewTemplate, IS_AVAILABLE, IS_DEFINED), (m_intUsingMode = ShowTemplateTowns)
'Le bouton de consultation est activé
cmdList.Enabled = True
End Sub

La procédure UnselectAllItem, comme son nom l'indique, est dédiée à désélectionner tous les éléments de la liste passée en paramètre.

En parallèle à cela, certains contrôles sont réinitialisés.

UnselectAllItem

Private Sub UnselectAllItem(ByRef TargetList As ListBox)
'Procédure dédiée à désélectionner tous les élements d'une liste
Dim I                                                  As Long
With TargetList
For I = 0 To TargetList.ListCount - 1
.Selected(I) = False
Next
End With
'On réinitialise les contrôles et les variables
Me.txtTemplateName = vbNullString
Me.lblTemplatePath.Caption = vbNullString
Me.lblTempletaID.Caption = vbNullString
m_intUsingMode = NotDefined
m_blnIsNewTemplate = False
End Sub

C'en est fini du code pour la classe du formulaire.

Maintenant, il reste quelques fonctions à ajouter, mais vous allez les écrire (ou plutôt les copier) dans un module externe...

VII - Les modules externes

Les autres fonctions et procédures exploitées par l'application font partie intégrante du projet.
Vous devez donc ajouter le module spécifié ci-après avec le code qui le compose.

VII-A - Le Module basFunctions

Ajoutez un nouveau module que vous nommez basFunctions.

Le bloc de déclarations contient la structure m_tyOpenFileName que vous connaissez certainement déjà.

Déclarations

'**********************************************************************
' Project           : WordTemplateManager
' Module            : basFunctions
' Type              : Module
' DateTime          : 13/05/2014
' Author            : Jean-Philippe AMBROSINO
' Review            :
' Review date       :
' Purpose           : Module used to provide special functions for application
'
'**********************************************************************
Option Compare Database
Option Explicit

Private Type m_tyOpenFileName
lStructSize                                            As Long
hwndOwner                                          As Long
hInstance                                          As Long
lpstrFilter                                        As String
lpstrCustomFilter                                  As String
nMaxCustFilter                                     As Long
nFilterIndex                                       As Long
lpstrFile                                          As String
nMaxFile                                           As Long
lpstrFileTitle                                     As String
nMaxFileTitle                                      As Long
lpstrInitialDir                                    As String
lpstrTitle                                         As String
Flags                                              As Long
nFileOffset                                        As Integer
nFileExtension                                     As Integer
lpstrDefExt                                        As String
lCustData                                          As Long
lpfnHook                                           As Long
lpTemplateName                                     As String
End Type

Private Declare Function GetOpenFileName Lib "comdlg32.dll" Alias "GetOpenFileNameA" (pOpenfilename As m_tyOpenFileName) As Long

La fonction OpenWordTemplateFile() récupère le nom complet d'un fichier sélectionné depuis la boîte de dialogue Ouvrir.

OpenWordTemplateFile

Public Function OpenWordTemplateFile(ByVal HWnd As Long, ByVal HInst As Long, ByVal Title As String, ByVal InitialDir As String, ByVal Filter As String, ByRef SelectedFile As String) As Double
'---------------------------------------------------------------------------
' Function      : OpenWordTemplateFile
' DateTime      : 13/05/2014
' Author        : Jean-Philippe AMBROSINO
' Purpose       : This Function is used to select a file from the FileDdialogBox
'...........................................................................
' Parameters    : HWnd : Handle de la fenêtre appelante
'                 HInst : Handle de l'instance
'                 Title : Titre de la fenêtre
'                 InitialDir : Dossier de départ
'                 Filter : Filtre sur fichier de type
'                 SelectedFile : Fichier sélectionné

' Return Codes  : Double = Taille du fichier en octets
'...........................................................................
' Notice        :
'---------------------------------------------------------------------------
Dim OFName                                             As m_tyOpenFileName
With OFName
.lStructSize = Len(OFName)
.hwndOwner = HWnd
.hInstance = HInst
.lpstrFilter = Filter
.lpstrFile = Space$(254)
.nMaxFile = 255
.lpstrFileTitle = Space$(254)
.nMaxFileTitle = 255
.lpstrInitialDir = InitialDir
.lpstrTitle = Title
.Flags = 0
If GetOpenFileName(OFName) Then
SelectedFile = Left$(.lpstrFile, InStrRev(.lpstrFile, vbNullChar) - 1)
OpenWordTemplateFile = FileLen(SelectedFile)
Else
OpenWordTemplateFile = 0
End If
End With
End Function

La fonction AddDirSeparator() ajoute une barre de fraction inversée pour délimiter un chemin.

AddDirSeparator

Public Function AddDirSeparator(ByVal PathName As String, Optional ByVal Separator As String = "\") As String
'---------------------------------------------------------------------------
' Function      : AddDirSeparator
' DateTime      : 13/05/2014
' Author        : Jean-Philippe AMBROSINO
' Purpose       : This Function is used to add a path separator
'...........................................................................
' Parameters    : PathName : Chemin à formater
'                 Separator : Séparateur à apposer

' Return Codes  : String = Chemin formaté
'...........................................................................
' Notice        :
'---------------------------------------------------------------------------
If Right$(PathName, 1) <> Separator Then
AddDirSeparator = PathName & Separator
Else
AddDirSeparator = PathName
End If
End Function

La fonction GetPathnameFromFullPath() retourne le nom du chemin d'un chemin complet.

GetPathnameFromFullPath

Public Function GetPathnameFromFullPath(ByVal PathName As String, KeepBackSlash As Boolean, Optional ByVal Separator As String = "\") As String
'---------------------------------------------------------------------------
' Function      : GetPathnameFromFullPath
' DateTime      : 13/05/2014
' Author        : Jean-Philippe AMBROSINO
' Purpose       : This Function is used to get the path from a full path
'...........................................................................
' Parameters    : PathName : Chemin à examiner
'                 KeepBackSlash : True si l'on conserve le séparateur de fin
'                 Separator : Séparateur à apposer

' Return Codes  : String = Chemin du fichier
'...........................................................................
' Notice        :
'---------------------------------------------------------------------------
If InStr(1, PathName, Separator, 1) Then
GetPathnameFromFullPath = Left$(PathName, InStrRev(PathName, Separator) - IIf(KeepBackSlash, 0, 1))
End If
End Function

La fonction GetFilenameFromFullPath() retourne le nom du fichier d'un chemin complet.

GetFilenameFromFullPath

Public Function GetFilenameFromFullPath(ByVal PathName As String, Optional ByVal Separator As String = "\") As String
'---------------------------------------------------------------------------
' Function      : GetFilenameFromFullPath
' DateTime      : 13/05/2014
' Author        : Jean-Philippe AMBROSINO
' Purpose       : This Function is used to get the file from a full path according to separator value
'...........................................................................
' Parameters    : PathName : Chemin à examiner
'                 Separator : Séparateur à identifier

' Return Codes  : String = Nom du fichier
'...........................................................................
' Notice        :
'---------------------------------------------------------------------------
If InStr(1, PathName, Separator, 1) Then
GetFilenameFromFullPath = Mid$(PathName, InStrRev(PathName, Separator) + 1)
End If
End Function

La fonction NotInList() permet de réagir avec l'événement NotInList des listes déroulantes.

NotInList

Public Function NotInList(ByRef F As Form, ByRef NewData As String, ByVal ItemSubject As String) As Long
'---------------------------------------------------------------------------
' Function      : NotInList
' DateTime      : 13/05/2014
' Author        : Jean-Philippe AMBROSINO
' Purpose       : This Function is used to show a message when a data is not belong to a Listbox
'...........................................................................
' Parameters    : F : Formulaire cible
'                 NewData : Nouvelle valeur
'                 ItemSubject : Sujet du champ concerné

' Return Codes  : Long = acDataErrContinue if Event is raised
'...........................................................................
' Notice        :
'---------------------------------------------------------------------------
MsgBox ItemSubject & " '" & NewData & "' ne fait pas partie de la liste !", 48, "Choix incorrect"
NotInList = acDataErrContinue
End Function

La fonction PluralSingular() transforme une occurrence au singulier ou au pluriel selon un tableau de correspondance prédéfini.

PluralSingular

Public Function PluralSingular(ByVal TextToChange As String, ByVal CountData As Long, Optional ByVal CharBefore As Integer = 32, Optional ByVal CharAfter As Integer = 0) As String
'---------------------------------------------------------------------------
' Function      : PluralSingular
' DateTime      : 13/05/2014
' Author        : Jean-Philippe AMBROSINO
' Purpose       : This Function is used to change the plural or singular of a given sentence
'...........................................................................
' Parameters    : TextToChange : Texte à mettre au pluriel
'                 CountData : Caractère à insérer avant
'                 CharAfter : Caractère à insérer après

' Return Codes  : String = Valeur retournée
'...........................................................................
' Notice        :
'---------------------------------------------------------------------------
Const SINGULAR_WORDS                                   As String = "le;la;du;dela;un;une;à;lequel;laquelle;ce;cet;cette;quelque;quel;quelle;a;été;est"
Const PLURAL_WORDS                                     As String = "les;les;des;des;des;des;aux;lesquels;lesquelles;ces;ces;ces;quelques;quels;quelles;ont;été;sont"
Dim straSingKeywords()                                 As String
Dim straPlurKeywords()                                 As String
Dim straSentence()                                     As String
Dim strNewWord                                         As String
Dim strCurrentWord                                     As String
Dim strSingKeyword                                     As String
Dim strNewSentence                                     As String
Dim blnKWfound                                         As Boolean
Dim I                                                  As Integer
Dim W                                                  As Integer

If CountData > 1 Then
TextToChange = Replace(TextToChange, " de la ", " dela ", , , 1)
straSentence = Split(TextToChange, " ")
straSingKeywords = Split(SINGULAR_WORDS, ";")
straPlurKeywords = Split(PLURAL_WORDS, ";")
For W = LBound(straSentence) To UBound(straSentence)
strCurrentWord = straSentence(W)
If Len(strCurrentWord) Then
For I = LBound(straSingKeywords) To UBound(straSingKeywords)
strSingKeyword = straSingKeywords(I)
If StrComp(strCurrentWord, strSingKeyword, 0) = 0 Then
strNewWord = straPlurKeywords(I)
blnKWfound = True
Exit For
End If
Next
If blnKWfound Then
strNewSentence = strNewSentence & strNewWord & Chr(32)
Else
strCurrentWord = strCurrentWord & "s "
strNewSentence = strNewSentence & strCurrentWord
End If
End If
blnKWfound = False
Next
Else
strNewSentence = TextToChange
End If
PluralSingular = IIf(CharBefore, Chr(CharBefore), "") & strNewSentence & IIf(CharAfter, Chr(CharAfter), "")
End Function

Une fois ce bloc terminé, pointez le menu Debogage et cliquez sur Compiler " Nom du projet ".
Aucune erreur ne doit apparaître.

Débogage du projet

Vous avez ici, terminé la conception du formulaire et le code qui l'accompagne, vous pouvez donc l'exécuter pour vos premiers tests.

Pour pouvoir tester, plusieurs solutions d'offrent à vous :

- créer une macro avec l'instruction OuvriFormulaire ;

- créer une procédure en VBA avec la méthode OpenForm de l'objet DoCmd ;

- ouvrir le formulaire en mode création et appuyer sur la touche F5 ;

- si le formulaire est fermé, l'ouvrir depuis le volet d'exploration.

Exemple d'ouverture en mode dialogue

DoCmd.OpenForm "Nom du formulaire", acNormal, , , , acDialog

VIII - Mise en exécution

Considérant que vous êtes censé n'avoir aucun modèle stocké (excepté le modèle par défaut, mais il n'est pas examiné), il est nécessaire d'afficher une information conséquente, traduite par ce message à l'ouverture :

Quoi qu'il en soit, qu'il y ait ou non ce message, le formulaire s'ouvre avec cet aspect.

Si l'utilisateur clique sur le bouton parcourir (représenté par...), la boîte de dialogue Ouvrir est affichée; il est alors invité à sélectionner un fichier Word de format 97-2003 ou 2007.

Si finalement il annule, la boîte se referme et un message l'informe en conséquence de son action.

Si l'utilisateur clique sur le bouton Consulter la liste,

et qu'ensuite il sélectionne un modèle,

la liste des communes affectées à ce dernier est affichée selon l'ordre de tri du groupe d'options.

Si l'utilisateur clique sur le bouton Modifier ce modèle, le même modèle est ré-affiché avec l'ensemble des communes listées selon l'ordre de tri du groupe d'options et dont celles qui lui sont affectées sont en surbrillance.

Si entre temps, l'utilisateur a effectivement ajouté ou supprimé au moins une ou plusieurs communes et qu'il clique sur le bouton Valider,

le formulaire se réinitialise (prêt à une opération similaire ou un ajout).

Il se présente en fait comme lors du premier chargement.

Pour chaque modèle listé, s'il existe un jeu de communes, celles-ci sont listées.

L'utilisateur peut alors supprimer les communes affectées au modèle pour le cas où il souhaiterait lui en affecter d'autres.

Cette action est confirmée par un message.

Durant cette action, le message informe alors que le modèle ne possède plus de communes et propose à l'utilisateur d'en ajouter.

Selon sa réponse, le formulaire s'affichera en conséquence.

S'il répond oui, le modèle est prêt à recevoir les communes que vous voulez lui affecter.

Il n'aura plus qu'à valider s'il veut valider son modèle.
S'il ne le fait pas, le modèle restera stocké sans communes et le formulaire retournera à son état de premier chargement.

L'utilisation reste somme toute très simple.

Elle peut toutefois être améliorée sur certains points, comme ceux que j'ai évoqués dans les paragraphes précédents.

IX - Mise en pratique du projet

Une fois que vous avez paramétré vos modèles, il est possible de tester en mode réel, ce que vous permet ce tutoriel.

Le principe, rappelez-vous, consiste à lancer une impression sur une fiche spécifique qui contient un enregistrement qui a été défini pour gérer le modèle cible (ici, des communes).

Si aucune commune ne peut être trouvée par rapport à celle de la fiche à imprimer, alors le modèle identifié 1 sera utilisé.
Sinon, ce sera le modèle qui aura été paramétré qui sera utilisé.

Dans les blocs de code qui vont suivre, j'ai écrit une routine assez basique qui est déclenchée par l'événement Click d'un bouton nommé cmdPrintToWord.
Considérez également que ce code doit être adapté pour votre propre projet d'une part et qu'il est incomplet d'autre part.

Scénario du déroulement :

1. On vérifie qu'une commune est sélectionnée et si tel est le cas ;

2. On recherche le modèle selon la commune ;

3. Puis on instancie de l'application Word ;

4. Et on affecte du modèle à une variable objet de type document ;

5. On affecte ensuite la collection de signets à une autre variable ;

6. On vérifie par une fonction que tous les signets existent ;

7. Le document se voit rempli ses signets avec la valeur des champs ;

8. Une fois rempli, on propose d'imprimer ;

9. Selon la réponse, on imprime et on quitte ou on affiche l'instance Word (prévoir un SetForeGround()).

Exemple de code pour l'impression (cmdPrintToWord_Click)

Private Sub cmdPrintToWord_Click()
Dim oWordApp                                           As Word.Application
Dim oDoc                                               As Word.Document
Dim oBookmarks                                         As Word.Bookmarks
Dim strErrMessage                                      As String
Dim strTemplateFilename                                As String
Dim lngIDTown                                          As Long
Dim lngIDRecord                                        As Long

On Error GoTo L_ErrcmdPrintToWord

lngIDTown = Nz(Me!cboTownList, 0)
'Si une commune est sélectionnée
If lngIDTown Then
'Récupération de l'ID de la fiche en cours
lngIDRecord = Me!IDRecord

'On recherche d'abord leu modèle selon la commune...
If GetWordTemplateFilename(lngIDTown, strTemplateFilename, strErrMessage) = False Then
'Une erreur est levée en cas d'échec
Err.Raise 3021, "Erreur interne", "Une erreur est survenue pendant que le programme recherchait le modèle cible :" & vbCrLf & strErrMessage
End If

'Instanciation de l'application Word
Set oWordApp = New Word.Application
With oWordApp

'Affectation du modèle à l'objet document
Set oDoc = .Documents.Add(Template:=strTemplateFilename, NewTemplate:=False, DocumentType:=0)

'Affectation de la collection de signets
Set oBookmarks = oDoc.Bookmarks
'Vérification des signets
If TemplateHasAllItsBookmarks(oDoc, oBookmarks, strErrMessage) = False Then
'[...]
'Code non développé ici
'[...]

'Une erreur est levée en cas d'échec
Err.Raise 17, "Signets manquants", "Un ou plusieurs signets n'ont pas été trouvés dans le modèle." & vbCrLf & vbCrLf & "Veuillez vous assurer que le modèle n'a pas été modifié."
End If

'Le modèle est ouvert et on remplit les signets avec la valeur des champs
If FillBookmarksFromRecordset(oDoc, oBookmarks, strErrMessage) = False Then
'[...]
'Code non développé ici
'[...]

'Une erreur est levée en cas d'échec
Err.Raise 17, "Remplissage échoué", "L'opération d'alimentation du document a échoué pour la raison suivante :" & vbCrLf & strErrMessage
End If

'Impression effective du document
If MsgBox("La génération est terminée :" & vbCrLf & vbCrLf & "Voulez-vous imprimer le document ?", vbQuestion + vbYesNo + vbDefaultButton2, "Imprimer ou garder") = vbYes Then
'On imprime
oDoc.PrintOut
'On quitte Word
.Quit
Else
'On affiche l'instance Word
.Visible = True
End If

End With
Else
MsgBox "Veuillez sélectionner une commune avant d'imprimer cette fiche.", vbExclamation, "Commune requise"
End If

On Error GoTo 0
L_ExcmdPrintToWord:
Set oDoc = Nothing
Set oBookmarks = Nothing
Exit Sub

L_ErrcmdPrintToWord:
MsgBox Err.Description, vbExclamation, Err.Source
oWordApp.Quit
Set oWordApp = Nothing
Resume L_ExcmdPrintToWord

End Sub

La fonction GetWordTemplateFilename() exécute une requête pour récupérer le modèle en fonction de la commune (pour ce cas précis).
Si le modèle n'est pas trouvé, c'est le modèle par défaut qui sera utilisé.

Exemple de code pour l'impression (GetWordTemplateFilename)

Private Function GetWordTemplateFilename(ByVal IDTown As Long, ByRef WordTemplate As String, ByRef ErrMessage As String) As Boolean
'Fonction permettant de récupérer le modèle selon la commune sélectionnée
Dim oRS                                                As DAO.Recordset
Dim strSQL                                             As String
Dim strTemplatePath                                    As String
Dim strTemplateName                                    As String

'La requête recherche le modèle en fonction de la ville
On Error GoTo L_ErrGetWordTemplateFilename

strSQL = "SELECT M.TemplatePath, M.TemplateName "
strSQL = strSQL & "FROM tblOutputReportTemplates AS M LEFT JOIN tblTemplatesAndTowns AS L ON M.TemplateID = L.TemplateID "
strSQL = strSQL & "WHERE (((M.IsUsed)=True) AND ((L.IDTown)=" & IDTown & "));"
Set oRS = CurrentDb.OpenRecordset(strSQL, dbOpenSnapshot)
If Not oRS.EOF Then
'On récupère le chemin complet du modèle
strTemplatePath = oRS.Fields("TemplatePath").Value
strTemplateName = oRS.Fields("TemplateName").Value
'On ferme le Recordset
oRS.Close
Else
'Si il n'y a pas de correspondance, on prend le modèle par défaut
strSQL = "SELECT TemplatePath, TemplateName FROM tblOutputReportTemplates WHERE TemplateID = 1;"
Set oRS = CurrentDb.OpenRecordset(strSQL, dbOpenSnapshot)
'On récupère le chemin complet du modèle
strTemplatePath = Nz(oRS.Fields("TemplatePath").Value, "")
strTemplateName = Nz(oRS.Fields("TemplateName").Value, "")
'On ferme le Recordset
oRS.Close
End If

'Le paramètre passé par référence prend la valeur trouvée
WordTemplate = AddDirSeparator(strTemplatePath) & strTemplateName

'Si le modèle est inexistant
If Len(Dir(WordTemplate, vbNormal)) = 0 Then
'On alimente le message d'erreur
ErrMessage = "Le modèle '" & strTemplateName & "' n'existe pas dans le dossier cible !"
Else
'Sinon, aucun message d'erreur
ErrMessage = vbNullString
End If
'La fonction prend True ou False selon la longueur du message d'erreur
GetWordTemplateFilename = (Len(ErrMessage) = 0)

On Error GoTo 0
L_ExGetWordTemplateFilename:
Exit Function

L_ErrGetWordTemplateFilename:
'L'erreur est remontée à la fonction appelante
ErrMessage = "(" & Err.Number & ") " & Err.Description
Resume L_ExGetWordTemplateFilename

End Function

La fonction TemplateHasAllItsBookmarks() est chargée de parcourir la collection de signets du document pour déterminer s'ils sont tous présents et qu'aucun n'a été supprimé.

N.B. les signets du document sont stockés dans une table et c'est de cette table que l'on effectue la comparaison.

Exemple de code pour l'impression (TemplateHasAllItsBookmarks)

Private Function TemplateHasAllItsBookmarks(ByRef Doc As Word.Document, ByRef BookmarksColl As Word.Bookmarks, ByRef ErrMessage As String) As Boolean
'Fonction non documentée
TemplateHasAllItsBookmarks = True
End Function

La fonction FillBookmarksFromRecordset() est chargée de remplir le document depuis un Recordset qui est alimenté avec un jeu de données correspondant à l'IDRecord de la fiche.

Exemple de code pour l'impression (FillBookmarksFromRecordset)

Private Function FillBookmarksFromRecordset(ByVal IDRecord As Long, ByRef Doc As Word.Document, ByRef BookmarksColl As Word.Bookmarks, ByRef ErrMessage As String) As Boolean
'Fonction non documentée
FillBookmarksFromRecordset = True
End Function

X - Conclusion

Ce petit tutoriel permettra aux personnes exigeantes à l'égard des applications de gestion et nécessitant des impressions sur des modèles Word d'apporter un plus à leur projet...
Si le besoin tourne autour du même thème, tant mieux, vous n'aurez pas de grosses modifications à apporter, mais dans le cas contraire, l'adaptation ne devrait pas poser trop de problèmes.

Je précise que j'ai volontairement modifié et simplifié quelques instructions pour que le code soit plus compréhensible pour tous...

De ce fait, les utilisateurs disposant d'un niveau avancé s'en apercevront et pourront adapter ces blocs selon leur propre méthode de développement.

XI - Sujets corrélatifs

Il existe quelques articles traitant de l'échange de données entre applications.
L'article Le publipostage avec Access peut être intéressant à consulter, notamment concernant la manipulation des signets (bookmarks).

XII - Remerciements

Je tiens à remercier tous celles et ceux qui ont participé à la relecture de ce document en y incluant leurs remarques et en particulier :

jacques_jean pour la relecture corrective,
f-leb pour la gestion et le suivi,

sans oublier
Nono40 pour son outil permettant la rédaction des articles.