Une application Access pour émettre et archiver des documents types Word

Claude Leloup

Ce tutoriel permet d'illustrer l'interaction Access/Word avec un exemple concret. Il s'agit d'une application qui montre comment émettre à la demande un document personnalisé basé sur une collection de modèles types.

Vos remarques et suggestions sont les bienvenues Commentez

- Titre : Une application Access pour émettre et archiver des documents types Word

- Auteur : Claude Leloup

- Parution : 14 janvier 2014

- Mise à jour : 31 juillet 2014

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Claude Leloup. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Le contexte de l'exemple

- I-B. L'idée directrice

- II. Structure de la base de données

- II-A. Le modèle de données

- II-B. Les références utiles

- III. Formulaire fPatients (1re partie) : présentation générale

- III-A. Choisir un patient

- III-B. Voir le dossier du patient

- III-B-1. Les deux formulaires fils sont ajustés à chaque enregistrement

- III-C. Émettre un document personnalisé

- IV. Formulaire fPatients (2e partie) : préparation de l'interfaçage avec Word

- IV-A. Créer la table tParametres

- IV-B. Créer le dossier du patient (si ce n'est déjà fait)

- V. Formulaire fPatients (3e partie) : l'appel à Word

- V-A. Avec des champs de fusion

- V-A-1. Conception du document type

- V-A-2. Le traitement dans Access

- V-B. Avec des signets

- V-B-1. Conception du document type

- V-B-2. Le traitement dans Access

- V-C. Avec des balises personnelles

- V-C-1. Conception du document type

- V-C-2. Le traitement dans Access

- VI. Formulaire fPatients (4e partie) : ouvrir le document personnalisé

- VII. Conclusion

- VIII. Annexe

- IX. Téléchargement

- X. Remerciements

I - Introduction

Le code proposé dans cet article contient des API, c'est-à-dire des fonctions externes déclarées avec l'instruction Declare Function ou Declare Sub.

Depuis Office 2010, Microsoft propose une version 64 bits.

Si vous exécutez un fichier contenant ce type de déclaration avec Office 2010 64 bits, ce code ne compile plus et vous aurez ce message

Une réécriture de ces instructions est nécessaire.

Vous trouverez la documentation nécessaire dans ce tutoriel : http://arkham46.developpez.com/articles/office/vba64bits/

I-A - Le contexte de l'exemple

L'utilisateur est un chirurgien spécialisé dans la pose de prothèses. Les patients qu'il reçoit lui sont adressés par leur médecin traitant (que le chirurgien devra informer du suivi par courrier).

L'objectif est d'établir des documents personnalisés au départ de modèles types tels que :

- des courriers adressés au médecin traitant du patient ;

- des comptes-rendus opératoires ;

- des comptes-rendus d'hospitalisation ;

- des ordonnances…

I-B - L'idée directrice

On va concevoir un formulaire qui affiche pour chaque patient l'ensemble des données utiles pour précompléter des documents types :

Des boutons permettent de choisir le document type :

Par exemple, celui-ci :

On interface alors Access avec Word pour créer le document personnalisé et le sauvegarder dans le dossier du patient :

Le document complété avec les données du patient s'affiche alors au premier plan. L'utilisateur peut ainsi y apporter les retouches ou ajouts éventuels avant de l'imprimer.

Lorsque l'utilisateur ferme le document personnalisé, l'application Access revient au premier plan.

II - Structure de la base de données

II-A - Le modèle de données

II-B - Les références utiles

III - Formulaire fPatients (1re partie) : présentation générale

III-A - Choisir un patient

La mise à jour de cboPatient déclenche ce code :

Private Sub cboPatient_AfterUpdate()
DoCmd.GoToControl "txtID"
DoCmd.FindRecord Me.cboPatient.Column(0)
Me.cboPatient = Empty
End Sub

On se positionne sur le contrôle qui contient la clé des enregistrements et on recherche l'enregistrement qui contient la valeur contenue dans cboPatient.

Le nom et le prénom d'un patient (deuxième colonne) sont affichés, mais c'est la première colonne (cachée) contenant tPatientsPK qui est liée.

III-B - Voir le dossier du patient

Le clic déclenche ce code :

1. Private Sub btVoir_Click()

2. Dim Chemin As String

3. Dim Rep As String

4. Chemin = CurrentProject.Path & "\DossiersPatients\" & Me.txtPatientNom & "_" & Me.txtPatientPrenom

5. 'Vérifier que le dossier existe

6. Rep = Dir(Chemin, vbDirectory)

7. If Len(Rep) = 0 Then

8. MsgBox "Il n'y a pas encore de dossier pour ce patient"

9. Exit Sub

10. End If

11. 'Afficher le dossier

12. Shell "C:\windows\explorer.exe " & Chr(34) & Chemin & Chr(34), vbMaximizedFocus

13. End Sub

Notes sur le code

4. On construit le chemin du répertoire ad hoc :

5-9. Un message si ce dossier est vide.

12. On affiche le contenu du dossier. À remarquer que ce dossier pourrait aussi contenir d'autres documents que ceux créés dans l'application : fichiers reçus de laboratoire, radios, etc. Un double-clic sur le nom d'un fichier ouvre celui-ci, à l'avant-plan, avec le programme que l'utilisateur a associé à l'extension.

III-B-1 - Les deux formulaires fils sont ajustés à chaque enregistrement

Si la notion de formulaire « père/fils » ne vous est pas familière, vous pouvez consulter ce tutoriel Comment classer les données dans des tables liées et construire un formulaire père/fils.

Lors de l'événement « Sur activation » (On current), c'est-à-dire à chaque changement d'enregistrement, on ajuste les deux formulaires fils pour qu'ils affichent respectivement la dernière consultation et la dernière hospitalisation du patient « activé ».

1. Private Sub Form_Current()

2. On Error GoTo GestionErreur

3. 'Ne rien faire si l'on crée un nouvel enregistrement

4. If Me.NewRecord Then Exit Sub

5. 'Pour sfPatientConsult : Afficher la dernière consultation

6. Me.CTNRsfConsult.Form.AllowAdditions = False

7. Me.CTNRsfConsult.SetFocus

8. DoCmd.GoToRecord , , acLast

9. 'Pour sfPatientConsult : Afficher la dernière hospitalisation

10. Me.CTNRsfHospita.Form.AllowAdditions = False

11. Me.CTNRsfHospita.SetFocus

12. DoCmd.GoToRecord , , acLast

13. Exit Sub

14. GestionErreur:

15. Select Case Err.Number

16. Case 2105 ' pas encore d'enregistrement

17. Resume Next

18. Case Else

19. MsgBox "Erreur Form_Current fPatient " & Err.Number & " " & Err.Description

20. End Select

21. End Sub

Notes sur le code

3-4. S'il s'agit d'un nouvel enregistrement (en cours de création), il est inutile d'aller plus loin : tConsultations et tHospitalisations ne contiennent pas encore d'enregistrement pour ce patient.

6-8 et 10-12. On met la propriété « Ajout autorisé » des sous-formulaires à Non (pour supprimer l'espace vierge qui serait réservé à l'ajout) et on affiche le dernier enregistrement… s'il existe.
S'il n'existe pas, une erreur 2105 est levée et traitée (ignorée) en 16-17.

III-C - Émettre un document personnalisé

Trois variables sont définies au niveau formulaire :

Option Compare Database
Option Explicit
Dim CheminDocType As String  'Chemin complet du document type
Dim DossierPat As String 'Chemin du dossier des documents du patient
Dim CheminDocPerso As String 'Chemin complet du document personnalisé

Chaque bouton correspond à un groupe de documents types.

Dans la propriété « Remarque » (Tag en anglais), on a inscrit le chemin du répertoire qui contient les documents types de ce groupe.

Au pire, nous aurions pu coder en dur l'adresse dans le programme, ou, déjà mieux, ajouter une table au modèle pour y loger les adresses correspondant à chaque bouton.

Cette solution ajoute de la flexibilité à l'application : si l'utilisateur veut créer un nouveau groupe, il lui suffit d'ajouter un bouton, d'inscrire le chemin du nouveau répertoire dans la propriété « Remarque » et de recopier le code « Sur clic ». (Nous verrons plus loin que les instructions sont identiques, quel que soit le bouton.)

La propriété Remarque (Tag) permet d'enregistrer toute information jugée utile concernant un formulaire, un état, une page d'accès aux données, une section ou un contrôle.
Contrairement aux autres propriétés, le paramétrage de la propriété Remarque (Tag) n'affecte pas les attributs d'un objet.
On peut y inscrire une chaine de maximum 2048 caractères.

Dans notre exemple, nous avons logé les documents types dans un sous-répertoire de l'application.

Tous ces boutons ont le même code : l'appel de la routine ChercherDocType :

Private Sub btCptesRendus_Click()
Call ChercherDocType
End Sub

que voici :

1. Private Sub ChercherDocType()

2. 'Ouvrir la boîte de sélection dans le répertoire ad hoc

3. CheminDocType = OuvrirUnFichier(Me.Hwnd, "Quel document type ?", 1, , , Eval(Me.ActiveControl.Tag))

4. 'Vérifiez qu'un fichier a été choisi

5. If Len(CheminDocType) <> 0 Then

6. Call ActualParam

7. Else

8. MsgBox "Vous devez choisir un document type"

9. End If

10. End Sub

Notes sur le code

3. On affiche la boîte de dialogue « Ouvrir un fichier » en démarrant la recherche dans le répertoire renseigné dans la propriété Remarque du bouton. Dans notre cas, la fonction Eval() se chargera de traduire en clair « CurrentProject.Path » (le répertoire qui contient l'application).

Pour le choix du document type, on utilise ici la boîte de dialogue Ouvrir de Windows. (Voyez dans la FAQ Access cette Q/R : Comment afficher la boîte de dialogue Ouvrir afin de récupérer le nom et le chemin du fichier sélectionné ?

Pour utiliser l'API, copiez le code fourni au chapitre VIII, dans un module (nommé par exemple: mFonctions).

Cette solution convient pour les versions Access2000 et suivantes. Mais, à partir des versions 2007, utilisez plutôt FileDialog : Boîtes de dialogue. (Documentation ici.)

5-9. Si un document type a été choisi, on appelle l'étape suivante : la routine « ActualParam », sinon on affiche une invite à choisir un document type.

N.B. À ce stade, la variable « CheminDocType » (définie en tête du module du formulaire) contient le chemin complet du document type choisi. Elle reste visible pour la suite du processus.

IV - Formulaire fPatients (2e partie) : préparation de l'interfaçage avec Word

IV-A - Créer la table tParametres

Les documents types contiennent des symboles génériques que nous allons devoir remplacer par leur valeur personnalisée par exemple, remplacer

Pour ce faire, nous allons construire la table tParametres.

Celle-ci n'aura qu'une seule ligne : les données construites d'après l'enregistrement en cours sur le formulaire et comportera les colonnes suivantes :

Pour la créer, nous exécuterons la requête rParametres dont voici

les tables en jeu :

et le SQL :

SELECT Date() AS DateJ, tPatients.PatientNom AS PatNom, tPatients.PatientPrenom AS PatPrenom,
IIf([PatientSexe]=-1,"M","F") AS PatSexe, IIf([PatientSexe]=0,"Madame","Monsieur") AS PatCiv,
tPatients.PatientDateNaiss AS PatNaiss, CInt((Date()-[PatientDateNaiss])/365) AS PatAge,
tPatients.PatientAdresse AS PatAdresse, tPatients.PatientLocalite AS PatLocalite,
IIf(IsNull([PatientAntecedents]),"RAS",[PatientAntecedents]) AS PatAntec,
IIf(IsNull([PatientAllergies]),"RAS",[PatientAllergies]) AS PatAllerg,
tConfreres.ConfrereNom AS MedTraitNom, tConfreres.ConfrerePrenom AS MedTraitPrenom,
IIf([tConfreres].[ConfrereSexe]=-1,"Cher confrère","Chère consœur") AS MedTraitCiv,
tConfreres.ConfreretAdresse AS MedTraitAdresse, tConfreres.ConfrereLocalite AS MedTraitLocalite,
tHospita.HospitaIN AS HospIN, tHospita.HospitaOUT AS HospOUT, tHospita.HospitaOpera AS HospDateOper,
tArticulations.ArticuNom AS HospARTICUL, IIf([HospitaCote]=1,"côté gauche",
IIf([HospitaCote]=2,"côté droit",IIf([HospitaCote]=3,"deux côtés","-"))) AS HospCOTE,
tConfreres_1.ConfrereNom AS HospANESTnom, tConfreres_1.ConfrerePrenom AS HospANESTprenom,
tConfreres_1.ConfreretAdresse AS HospANESTadresse, tConfreres_1.ConfrereLocalite AS HospANESTlocalite,
IIf(tConfreres_1.ConfrereSexe=-1,"Cher confrère","Chère consœur") AS HospANESTCiv INTO tParametres
FROM (tPatients LEFT JOIN tConfreres ON tPatients.tConfreresFK = tConfreres.tConfreresPK)
LEFT JOIN ((tHospita LEFT JOIN tArticulations ON tHospita.tArticulationsFK = tArticulations.tArticulationsPK)
LEFT JOIN tConfreres AS tConfreres_1 ON tHospita.tConfreresPK = tConfreres_1.tConfreresPK)
ON tPatients.tPatientsPK = tHospita.tPatientsFK
WHERE (((tPatients.tPatientsPK)=[Formulaires]![fPatients]![txtId]));

Dans notre processus, c'est dans la sub ActualParam que cela se passe :

Private Sub ActualParam()
Me.Refresh
'Actualiser la tParametres
DoCmd.SetWarnings False
DoCmd.OpenQuery "rParametres"
DoCmd.SetWarnings True
Call CreerDosPatient
End Sub

IV-B - Créer le dossier du patient (si ce n'est déjà fait)

1. Private Sub CreerDosPatient()

2. On Error GoTo GestionErreurs

3. 'Créer le dossier Patient si nécessaire

4. DossierPat = CurrentProject.Path & "\DossiersPatients\" & Me.txtPatientNom _

5. & "_" & Me.txtPatientPrenom

6. MkDir DossierPat

7. Call CreerDoc

8. Exit Sub

9. GestionErreurs:

10. Select Case Err.Number

11. Case 75 'le dossier existe déjà

12. Resume Next

13. Case Else

14. MsgBox "Erreur dans CreerDosPatient" & " " & Err.Number & " " & Err.Description

15. End Select

16. End Sub

Notes sur le code

4-6. On crée, si nécessaire, un sous-répertoire au nom du patient. S'il s'avère que ce dossier existe déjà, une erreur N° 75 est levée, mais elle est gérée en 11-12.

À ce stade, la variable « DossierPat » contient le chemin du dossier du patient, disponible pour la suite.

nous pouvons maintenant appeler Word pour faire la mise au net du document personnalisé.

V - Formulaire fPatients (3e partie) : l'appel à Word

Plusieurs techniques sont possibles pour la conception des documents types. On peut

- soit utiliser la technique des champs de fusion ;

- soit utiliser des signets ;

- soit encore utiliser une convention de balises personnelles.

Nous allons passer en revue les diverses méthodes, en expliquant comment s'y prendre pour construire le document type et décrire ensuite comment piloter Word avec ce document.

V-A - Avec des champs de fusion

V-A-1 - Conception du document type

C'est l'approche « publipostage », à une nuance près : dans notre cas, il ne s'agit pas de créer une masse de lettres personnalisées, mais seulement une seule à la fois, destinée au patient.

Dans la base de données Access, nous disposons de la table tParametres avec autant de colonnes que de champs de fusion utiles, et qui ne contient qu'une seule ligne avec les valeurs du patient sélectionné dans le formulaire.

Dans cette hypothèse, une fois le texte fixe rédigé, il faut insérer les « champs de fusion », par exemple en se laissant guider par l'assistant de Word :

(Publipostage>Démarrer la fusion et le publipostage>Assistant Fusion et publipostage pas à pas…)

Une nouvelle fenêtre apparaît :

et vous passez aux étapes suivantes en acceptant chaque fois la proposition par défaut jusqu'au choix de la base de données de référence (dans notre exemple benjaminCF.mdb).

Vous êtes invité à choisir dans la base de données, la table de référence (dans notre cas, tParametres) :

et une nouvelle fenêtre affiche le nom des champs disponibles :

Remarquez : les dates au format américain !

Vous cliquez OK et vous passez à l'étape 4

Vous placez le curseur à l'endroit où vous voulez insérer une valeur personnalisée, vous cliquez sur « Autres éléments… »

et vous cliquez sur le nom du champ souhaité à cet endroit.

L'assistant insère alors la bonne syntaxe et vous pouvez répéter pour les autres champs, par exemple pour obtenir ceci :

dans le but d'obtenir ce résultat :

Vous pouvez aussi utiliser un raccourci : <CTRL + F9> qui insérera la balise que vous pourrez compléter à la main avec « MERGEFIELD "NomDuChamp" » entre les accolades.
N.B. Utilisez <CTRL + F9> et non simplement les touches accolade ouvrante et accolade fermante : le résultat doit avoir cet aspect :
et pas simplement : {MERGEFIELD "NomDuChamp"}.

Pour les champs de type date, il faut ajouter un « commutateur d'image » pour que la date s'affiche à l'européenne : Jour/Mois/Année comme ceci :

Voici la syntaxe des champs de fusion prévus dans l'application qui nous sert d'exemple :

V-A-2 - Le traitement dans Access

Pour rappel, nous sommes ici :

Il s'agit, dans ce choix de technique d'ouvrir depuis Access le document type, de déclencher le processus de publipostage et d'archiver l'exemplaire personnalisé dans le dossier du patient.

1. Private Sub CreerDoc()

2. Dim wdapp As Word.Application

3. Dim NomDoc As String

4. Set wdapp = New Word.Application

5. CheminDocPerso = DossierPat & "\" & NomFichier(CheminDocType) _

6. & "_" & Format(Date, "yyyymmdd") & ".docx"

7. With wdapp

8. .Visible = True

9. ' Ouvrir le document type

10. .Documents.Open CheminDocType

11. .ActiveDocument.MailMerge.OpenDataSource _

12. Name:=CurrentDb.Name, _

13. LinkToSource:=True, _

14. Connection:="Table tParametres", _

15. SQLStatement:="SELECT * FROM [tParametres]"

16. .ActiveDocument.MailMerge.Execute

17. .ActiveDocument.SaveAs2 CheminDocPerso

18. .Documents.Close

19. End With

20. ' Fermer et libérer les objets

21. wdapp.Quit

22. Set wdapp = Nothing

23. ' Ouvrir le doc perso

24. Call OuvrirDocPerso

25. End Sub

Notes sur le code

4. On interface Access avec Word.

5. On construit le chemin du document personnalisé (dans le dossier du patient, le nom du document type, suffixé de la date du jour).

8-16. On déclenche la procédure de publipostage dans Word, avec le document type choisi et la table tParametres comme base de données de référence.

17. On copie le résultat personnalisé en lui donnant le nom construit au point 5.

18. On referme tous les documents.

19-22. On quitte la session Word.

24. On passe à l'étape suivante.

V-B - Avec des signets

V-B-1 - Conception du document type

Dans un document Word, un signet identifie un emplacement.

Voici une manière de procéder.

1° D'abord rédiger le texte type. Là où il convient de personnaliser, on inscrit un repère : un mot qui a une signification pour nous, par exemple le nom de la colonne qui contient la valeur personnalisée.
Comme ceci :

2° On sélectionne chaque repère et on y insère un signet :

S'ouvre alors une fenêtre qui vous permet de nommer ce signet :

L'astuce va consister à introduire dans le texte rédigé en Word, des signets dont le nom correspond au nom de la colonne dans la table de référence.

3° Pour mieux visualiser, on va choisir l'option d'afficher les signets.
Fichier/Options/Options avancées et cocher « Afficher les signets » :

Cette fois le texte prend cet aspect :

Chaque signet est affiché encadré de crochets.

Il n'est pas possible d'insérer plusieurs fois un signet portant le même nom !

Par exemple, si le nom du patient doit figurer à divers endroits dans le document type, il n'est pas possible d'insérer plusieurs fois un signet nommé « PatNom ». (Si vous le faites, seul le dernier inséré sera considéré.)

Qu'à cela ne tienne, on peut contourner le problème en suffixant le nom du signet, par exemple « PatNom_1 », « PatNom_NimporteQuoi »… Et bien entendu en tenir compte dans le programme Access au moment de la substitution du signet par sa valeur personnalisée. (Nous verrons comment plus loin.)

V-B-2 - Le traitement dans Access

Pour rappel, nous sommes ici :

Ici, l'approche est différente : nous allons prendre une copie du document type, l'ouvrir et remplacer chaque signet contenu dans le document par la valeur personnalisée correspondante.

Puisque nous rencontrerons peut-être des signets suffixés (Signet_qqchose), voici une petite fonction qui permettra d'élimer le suffixe :

1. Private Function Tronca(original As String) As String

2. ' restitue la partie avant le "_"

3. Dim Arr() As String

4. Arr = Split(original, "_")

5. Tronca = Arr(0)

6. End Function

Notes sur le code

3. On définit une variable sous forme d'un tableau.

4. La fonction Split() va donc alimenter chaque cellule du tableau avec les morceaux du texte original séparés par des « _ ».

Ainsi,

- si le texte original est « PatNom_qqchose », la première cellule - Arr(0) - contiendra « PatNom » et la seconde - Arr(1) - contiendra « qqchose » ;

- si le texte original est « PatNom », seule Arr(0) sera alimentée et contiendra « PatNom ».

5. Tronca(LeSignetDuDocument) nous restituera donc le nom de la colonne de tParametres, que « LeSignetDuDocument » contienne ou non un suffixe.

Examinons maintenant la création du document :

1. Private Sub CreerDoc()

2. Dim wdapp As Word.Application

3. Dim NomDoc As String

4. Dim rs As DAO.Recordset

5. Dim bkm As Word.Bookmark

6. NomDoc = NomFichier(CheminDocType)

7. Me.Refresh

8. CheminDocPerso = DossierPat & "\" & NomFichier(CheminDocType)  _

9. & "_" & Format(Date, "yyyymmdd") & ".docx"

10. FileCopy CheminDocType, CheminDocPerso

11. 'Créer un RecordSet avec l'enregistrement de tParametres

12. Set rs = CurrentDb.OpenRecordset("tParametres")

13. 'Démarrer Word

14. Set wdapp = New Word.Application

15. With wdapp

16. .Visible = False

17. 'Ouvrir le document à personnaliser

18. .Documents.Open CheminDocPerso

19. 'Remplacer tous les signets par leur valeur

20. For Each bkm In .ActiveDocument.Bookmarks

21. .ActiveDocument.Bookmarks(bkm.Name).Range.Text = rs.Fields((Tronca(bkm.Name))).Value

22. Next bkm

23. 'Sauvegarder

24. .ActiveDocument.Save

25. 'Fermer et libérer les objets

26. .Quit

27. End With

28. Set wdapp = Nothing

29. rs.Close

30. Set rs = Nothing

31. ' Ouvrir le doc perso

32. Call OuvrirDocPerso

33. End Sub

Notes sur le code

10. Copie du document type vers le document à personnaliser.

12. On crée un recordset avec l'enregistrement de tParametres.

14. On ouvre une session Word.

18. On ouvre le document à personnaliser.

19-22. On passe en revue chaque signet (bookmark) et on le remplace par la valeur de la colonne correspondante de rs (donc de tParametres).

Le document type contient une collection de signets (au moins un !).

For Each bkmva déclencher une boucle dans la collection de signets : chaque signet du document sera remplacé par la valeur personnalisée qui lui correspond dans le recordset (en fait, la table tParametres).

24. On sauvegarde le document, maintenant personnalisé.

26-30. On quitte proprement.

32. On passe à l'étape suivante.

V-C - Avec des balises personnelles

V-C-1 - Conception du document type

La technique ressemble à la précédente, c'est le traitement Access qui sera différent.

On rédige le document type en y insérant des repères (le nom des champs) aux endroits à personnaliser. On encadre chaque repère avec deux caractères qui serviront de balises dans le programme Access pour déterminer où commence et où se termine le symbole qui devra être remplacé par la valeur personnalisée.

Pour éviter les confusions, il faut choisir un caractère qui a peu de chances de se retrouver dans le texte type. Par exemple, une barre verticale « | » (un « pipe » = tube en anglais :caractère ASCII 124).

Le document type aura donc cet aspect :

Ici, pas de souci : le même symbole peut être utilisé plusieurs fois dans le document type.

V-C-2 - Le traitement dans Access

Pour rappel, nous sommes ici :

Cette technique ressemble à la précédente. Sauf que nous allons systématiquement, pour chaque valeur personnalisée, construire une balise et lancer une action « Rechercher/Remplacer » pour y substituer la valeur personnalisée s'il s'avère que le document contient cette balise.

1. Private Sub CreerDoc()

2. Dim wdapp As Word.Application

3. Dim NomDoc As String

4. Dim rs As DAO.Recordset

5. Dim i As Integer

6. NomDoc = NomFichier(CheminDocType)

7. Me.Refresh

8. CheminDocPerso = DossierPat & "\" & NomFichier(CheminDocType)  _

9. & "_" & Format(Date, "yyyymmdd") & ".docx"

10. FileCopy CheminDocType, CheminDocPerso

11. 'Créer un RecordSet avec l'enregistrement de tParametres

12. Set rs = CurrentDb.OpenRecordset("tParametres")

13. 'Démarrer Word

14. Set wdapp = New Word.Application

15. With wdapp

16. .Visible = False

17. 'Ouvrir le document à personnaliser

18. .Documents.Open CheminDocPerso

19. 'Rechercher et remplacer toutes les balises par leur valeur personnalisée

20. For i = 0 To rs.Fields.Count - 1

21. .Selection.Find.Execute FindText:="|" & rs(i).Name & "|", ReplaceWith:=rs(i), Replace:=wdReplaceAll

22. Next i

23. 'Sauvegarder

24. .ActiveDocument.Save

25. 'Fermer et libérer les objets

26. .Quit

27. End With

28. Set wdapp = Nothing

29. rs.Close

30. Set rs = Nothing

31. ' Ouvrir le doc perso

32. Call OuvrirDocPerso

33. End Sub

Notes sur le code

Seules les lignes 20-22 diffèrent de la version avec signets. Pour chaque colonne de rs (donc de tParametres), on construit une balise et on la remplace par sa valeur personnalisée.

En fait, l'instruction .Selection.Find.Execute FindText:="|" & rs(i).Name & "|", ReplaceWith:=rs(i), Replace:=wdReplaceAll correspond à ce que vous feriez à la main dans le traitement de texte pour remplacer une balise symbole par sa valeur personnalisée :

Et comme, a priori, on ignore quels sont les symboles présents dans le document, on tente le remplacement pour les toutes les valeurs personnalisables, c'est l'objet de la boucle

For i = 0 To rs.Fields.Count - 1.

N.B. Nous aurions pu déclencher cette boucle en définissant une variable fld et l'instruction For Each, comme ceci :

Dim fld As DAO.Field
...
'Rechercher et remplacer toutes les balises par leur valeur personnalisée
For Each fld In rs.Fields
.Selection.Find.Execute FindText:="|" & fld.Name & "|", ReplaceWith:=rs.Fields(fld.Name).Value, Replace:=wdReplaceAll
Next fld

Remarquez la différence entre l'approche « balises personnelles » et les autres.

Ici, nous devons tenter de remplacer toutes les valeurs personnalisables possibles, qu'elles soient présentes ou non dans le document. C'est donc moins performant… mais probablement sans conséquence perceptible par l'utilisateur puisqu'il n'y a qu'un seul document à traiter.

VI - Formulaire fPatients (4e partie) : ouvrir le document personnalisé

Private Sub OuvrirDocPerso()
Shell "C:\WINDOWS\EXPLORER.EXE " & CheminDocPerso
End Sub

L'instruction Shell "C:\WINDOWS\EXPLORER.EXE " & LeCheminDunFichierest l'équivalent d'un double-clic sur le nom du fichier dans l'explorateur : le fichier s'ouvre avec le programme que l'utilisateur a associé à l'extension du fichier.

Dans notre cas, il s'agit d'un .doc(x), c'est donc Word qui prend la main.

On ouvre le document personnalisé : l'écran affiche en premier plan le document. L'utilisateur peut le vérifier et y apporter les retouches éventuelles. Lorsqu'il ferme le document, le formulaire fPatients réapparaît à l'écran.

VII - Conclusion

Pour émettre les documents personnalisés, nous aurions pu chercher une solution « tout Access » en utilisant des états. À chaque document type, un état avec du texte fixe et des champs que nous aurions complétés par programmation.
C'eut été incontestablement plus lourd et plus difficile à faire évoluer que la solution proposée dans ce tutoriel.
Autant faire l'effort d'apprivoiser le VBA de Word pour profiter des fonctionnalités offertes par ce traitement de texte. Difficile aussi d'égaler avec Access la qualité d'un document final produit par Word.

Si on interagit avec Word, vaut-il mieux utiliser des champs de fusion, des signets ou des balises personnelles ?

Disons d'emblée que, selon les spécialistes, le recours aux balises personnelles est la solution la moins performante (plus lente), encore que lorsqu'il s'agit de produire, comme dans notre exemple, un document personnalisé à la fois, l'argument tient peu : la différence est imperceptible pour l'utilisateur.
À noter aussi une difficulté supplémentaire si le texte variable se trouve dans l'en-tête ou le pied de page du document : il faut les activer avant la recherche.

Quant à choisir entre champs de fusion ou signets, c'est une question d'habitude : si le publipostage vous est familier, vous choisirez sans doute les champs de fusion.

Si vous optez pour les signets, pensez à l'astuce de les « suffixer » si vous avez besoin de mentionner plusieurs fois la même valeur dans un document.

Dans le document type, vous pouvez aussi utiliser un champ STYLEREF. Ce champ permet de répéter la dernière occurrence trouvée pour un style choisi. La technique est décrite dans cette Q/R de la FAQ Word : Comment faire pour répéter la même donnée plusieurs fois dans un même document ?

Quoi qu'il en soit, pensez à donner aux signets ou aux balises des noms similaires à ceux des champs de la table de référence dans Access.

VIII - Annexe

Pour ouvrir la boîte de dialogue avec l'API Windows, copiez ce code dans un module (nommé par exemple: mFonctions) :

Option Compare Database
Option Explicit

'Déclaration de l'API
Private Declare Sub PathStripPath Lib "shlwapi.dll" Alias "PathStripPathA" (ByVal pszPath As String)
Private Declare Function GetOpenFileName Lib "comdlg32.dll" Alias _
"GetOpenFileNameA" (pOpenfilename As OPENFILENAME) As Long

'Structure du fichier
Private Type OPENFILENAME
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

'Constantes
Private Const OFN_READONLY = &H1
Private Const OFN_OVERWRITEPROMPT = &H2
Private Const OFN_HIDEREADONLY = &H4
Private Const OFN_NOCHANGEDIR = &H8
Private Const OFN_SHOWHELP = &H10
Private Const OFN_ENABLEHOOK = &H20
Private Const OFN_ENABLETEMPLATE = &H40
Private Const OFN_ENABLETEMPLATEHANDLE = &H80
Private Const OFN_NOVALIDATE = &H100
Private Const OFN_ALLOWMULTISELECT = &H200
Private Const OFN_EXTENSIONDIFFERENT = &H400
Private Const OFN_PATHMUSTEXIST = &H800
Private Const OFN_FILEMUSTEXIST = &H1000
Private Const OFN_CREATEPROMPT = &H2000
Private Const OFN_SHAREAWARE = &H4000
Private Const OFN_NOREADONLYRETURN = &H8000
Private Const OFN_NOTESTFILECREATE = &H10000

Private Const OFN_SHAREFALLTHROUGH = 2
Private Const OFN_SHARENOWARN = 1
Private Const OFN_SHAREWARN = 0

Private Const OFN_EXPLORER = &H80000

Public Function OuvrirUnFichier(Handle As Long, _
Titre As String, _
TypeRetour As Byte, _
Optional TitreFiltre As String, _
Optional TypeFichier As String, _
Optional RepParDefaut As String) As String
'OuvrirUnFichier est la fonction à utiliser dans votre formulaire pour ouvrir _
'la boîte de dialogue de sélection d'un fichier.
'Explication des paramètres
'Handle = le handle de la fenêtre (Me.Hwnd)
'Titre = Titre de la boîte de dialogue
'TypeRetour (Définit la valeur, de type String, renvoyée par la fonction)
'1 = Chemin complet + Nom du fichier
'2 = Nom fichier seulement
'TitreFiltre = Titre du filtre
'Exemple: Fichier Access
'N'utilisez pas cet argument si vous ne voulez spécifier aucun filtre
'TypeFichier = Extention du fichier (Sans le .)
'Exemple: MDB
'N'utilisez pas cet argument si vous ne voulez spécifier aucun filtre
'RepParDefaut = Répertoire d'ouverture par défaut
'Exemple: C:\windows\system32
'Si vous laissez l'argument vide, par défaut il se place dans le répertoire de votre application

Dim StructFile As OPENFILENAME
Dim sFiltre As String

'Construction du filtre en fonction des arguments spécifiés
If Len(TitreFiltre) > 0 And Len(TypeFichier) > 0 Then
sFiltre = TitreFiltre & " (" & TypeFichier & ")" & Chr$(0) & "*." & TypeFichier & Chr$(0)
End If
sFiltre = sFiltre & "Tous (*.*)" & Chr$(0) & "*.*" & Chr$(0)

'Configuration de la boîte de dialogue
With StructFile
.lStructSize = Len(StructFile) 'Initialisation de la grosseur de la structure
.hwndOwner = Handle 'Identification du handle de la fenêtre
.lpstrFilter = sFiltre 'Application du filtre
.lpstrFile = String$(254, vbNullChar) 'Initialisation du fichier '0' x 254
.nMaxFile = 254 'Taille maximale du fichier
.lpstrFileTitle = String$(254, vbNullChar) 'Initialisation du nom du fichier '0' x 254
.nMaxFileTitle = 254  'Taille maximale du nom du fichier
.lpstrTitle = Titre 'Titre de la boîte de dialogue
.flags = OFN_HIDEREADONLY  'Option de la boîte de dialogue
If ((IsNull(RepParDefaut)) Or (RepParDefaut = "")) Then
RepParDefaut = CurrentDb.Name
PathStripPath (RepParDefaut)
.lpstrInitialDir = Left(CurrentDb.Name, Len(CurrentDb.Name) - Len(Mid$(RepParDefaut, 1, _
InStr(1, RepParDefaut, vbNullChar) - 1)))
Else: .lpstrInitialDir = RepParDefaut
End If
End With

If (GetOpenFileName(StructFile)) Then 'Si un fichier est sélectionné
Select Case TypeRetour
Case 1: OuvrirUnFichier = Trim$(Left(StructFile.lpstrFile, InStr(1, StructFile.lpstrFile, vbNullChar) - 1))
Case 2: OuvrirUnFichier = Trim$(Left(StructFile.lpstrFileTitle, InStr(1, StructFile.lpstrFileTitle, vbNullChar) - 1))
End Select
End If

End Function

Ajoutez-y cette fonction qui permet d'isoler le nom d'un fichier si on connaît son chemin complet :

1. Public Function NomFichier(Chemin As String) As String

2. Dim i As Integer

3. i = 1

4. Do

5. If Mid(Chemin, Len(Chemin) - i, 1) = "\" Then

6. NomFichier = Right(Chemin, i)

7. Exit Function

8. Else

9. i = i + 1

10. End If

11. Loop

12. End Function

Notes sur le code

4-11. On lit le chemin de droite à gauche, en cherchant le dernier « \ », à droite duquel est mentionné le nom du fichier.

IX - Téléchargement

Les bases de données qui ont servi d'exemple :

version avec champs de fusion ;

version avec signets ;

version avec balises.

Veuillez décompresser l'archive dans un répertoire de votre choix.

Vos remarques et suggestions sont les bienvenues Commentez

X - Remerciements

À (Zoom61) et Pierre Fauconnier pour leurs remarques.

À Christophe Warin (Tofalu) qui a corrigé mon code et mis de l'ordre dans l'exposé.

À Olivier Lebeau (Heureux-oli) pour toutes ses contributions dans la sphère de Word.

À Arkham46 pour l'outil qui a facilité la rédaction et la mise en ligne de cet article.

À Philippe Duval (Phanloga) pour la correction orthographique.