IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

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

Commentez Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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.

Image non disponible
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 Word
La table des modèles
La table de liaison modèles/communes
La table de liaison modèles/communes
La table des 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 du formulaire
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
Sélectionnez
'**********************************************************************
' 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()
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
'**********************************************************************
' 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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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.

Image non disponible
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
Sélectionnez
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 :

Image non disponible

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

Image non disponible

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.

Image non disponible

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

Image non disponible

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

Image non disponible

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

Image non disponible

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

Image non disponible

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.

Image non disponible

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

Image non disponible

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.

Image non disponible

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

Image non disponible

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.

Image non disponible

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

Image non disponible

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.

Image non disponible

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)
Sélectionnez
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)
Sélectionnez
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)
Sélectionnez
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)
Sélectionnez
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 AccessLe 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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

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.