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 ▲
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 SubOn 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
Private Sub btVoir_Click()
Dim Chemin As String
Dim Rep As String
Chemin = CurrentProject.Path & "\DossiersPatients\" & Me.txtPatientNom & "_" & Me.txtPatientPrenom
'Vérifier que le dossier existe
Rep = Dir(Chemin, vbDirectory)
If Len(Rep) = 0 Then
MsgBox "Il n'y a pas encore de dossier pour ce patient"
Exit Sub
End If
'Afficher le dossier
Shell "C:\windows\explorer.exe " & Chr(34) & Chemin & Chr(34), vbMaximizedFocus
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é ».
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
Private Sub Form_Current()
On Error GoTo GestionErreur
'Ne rien faire si l'on crée un nouvel enregistrement
If Me.NewRecord Then Exit Sub
'Pour sfPatientConsult : Afficher la dernière consultation
Me.CTNRsfConsult.Form.AllowAdditions = False
Me.CTNRsfConsult.SetFocus
DoCmd.GoToRecord , , acLast
'Pour sfPatientConsult : Afficher la dernière hospitalisation
Me.CTNRsfHospita.Form.AllowAdditions = False
Me.CTNRsfHospita.SetFocus
DoCmd.GoToRecord , , acLast
Exit Sub
GestionErreur:
Select Case Err.Number
Case 2105 ' pas encore d'enregistrement
Resume Next
Case Else
MsgBox "Erreur Form_Current fPatient " & Err.Number & " " & Err.Description
End Select
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 Subque voici :
2.
3.
4.
5.
6.
7.
8.
9.
10.
Private Sub ChercherDocType()
'Ouvrir la boîte de sélection dans le répertoire ad hoc
CheminDocType = OuvrirUnFichier(Me.Hwnd, "Quel document type ?", 1, , , Eval(Me.ActiveControl.Tag))
'Vérifiez qu'un fichier a été choisi
If Len(CheminDocType) <> 0 Then
Call ActualParam
Else
MsgBox "Vous devez choisir un document type"
End If
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 SubIV-B. Créer le dossier du patient (si ce n'est déjà fait)▲

2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
Private Sub CreerDosPatient()
On Error GoTo GestionErreurs
'Créer le dossier Patient si nécessaire
DossierPat = CurrentProject.Path & "\DossiersPatients\" & Me.txtPatientNom _
& "_" & Me.txtPatientPrenom
MkDir DossierPat
Call CreerDoc
Exit Sub
GestionErreurs:
Select Case Err.Number
Case 75 'le dossier existe déjà
Resume Next
Case Else
MsgBox "Erreur dans CreerDosPatient" & " " & Err.Number & " " & Err.Description
End Select
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
Private Sub CreerDoc()
Dim wdapp As Word.Application
Dim NomDoc As String
Set wdapp = New Word.Application
CheminDocPerso = DossierPat & "\" & NomFichier(CheminDocType) _
& "_" & Format(Date, "yyyymmdd") & ".docx"
With wdapp
.Visible = True
' Ouvrir le document type
.Documents.Open CheminDocType
.ActiveDocument.MailMerge.OpenDataSource _
Name:=CurrentDb.Name, _
LinkToSource:=True, _
Connection:="Table tParametres", _
SQLStatement:="SELECT * FROM [tParametres]"
.ActiveDocument.MailMerge.Execute
.ActiveDocument.SaveAs2 CheminDocPerso
.Documents.Close
End With
' Fermer et libérer les objets
wdapp.Quit
Set wdapp = Nothing
' Ouvrir le doc perso
Call OuvrirDocPerso
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 :
2.
3.
4.
5.
6.
Private Function Tronca(original As String) As String
' restitue la partie avant le "_"
Dim Arr() As String
Arr = Split(original, "_")
Tronca = Arr(0)
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
Private Sub CreerDoc()
Dim wdapp As Word.Application
Dim NomDoc As String
Dim rs As DAO.Recordset
Dim bkm As Word.Bookmark
NomDoc = NomFichier(CheminDocType)
Me.Refresh
CheminDocPerso = DossierPat & "\" & NomFichier(CheminDocType) _
& "_" & Format(Date, "yyyymmdd") & ".docx"
FileCopy CheminDocType, CheminDocPerso
'Créer un RecordSet avec l'enregistrement de tParametres
Set rs = CurrentDb.OpenRecordset("tParametres")
'Démarrer Word
Set wdapp = New Word.Application
With wdapp
.Visible = False
'Ouvrir le document à personnaliser
.Documents.Open CheminDocPerso
'Remplacer tous les signets par leur valeur
For Each bkm In .ActiveDocument.Bookmarks
.ActiveDocument.Bookmarks(bkm.Name).Range.Text = rs.Fields((Tronca(bkm.Name))).Value
Next bkm
'Sauvegarder
.ActiveDocument.Save
'Fermer et libérer les objets
.Quit
End With
Set wdapp = Nothing
rs.Close
Set rs = Nothing
' Ouvrir le doc perso
Call OuvrirDocPerso
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
Private Sub CreerDoc()
Dim wdapp As Word.Application
Dim NomDoc As String
Dim rs As DAO.Recordset
Dim i As Integer
NomDoc = NomFichier(CheminDocType)
Me.Refresh
CheminDocPerso = DossierPat & "\" & NomFichier(CheminDocType) _
& "_" & Format(Date, "yyyymmdd") & ".docx"
FileCopy CheminDocType, CheminDocPerso
'Créer un RecordSet avec l'enregistrement de tParametres
Set rs = CurrentDb.OpenRecordset("tParametres")
'Démarrer Word
Set wdapp = New Word.Application
With wdapp
.Visible = False
'Ouvrir le document à personnaliser
.Documents.Open CheminDocPerso
'Rechercher et remplacer toutes les balises par leur valeur personnalisée
For i = 0 To rs.Fields.Count - 1
.Selection.Find.Execute FindText:="|" & rs(i).Name & "|", ReplaceWith:=rs(i), Replace:=wdReplaceAll
Next i
'Sauvegarder
.ActiveDocument.Save
'Fermer et libérer les objets
.Quit
End With
Set wdapp = Nothing
rs.Close
Set rs = Nothing
' Ouvrir le doc perso
Call OuvrirDocPerso
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 fldRemarquez 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 SubL'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 FunctionAjoutez-y cette fonction qui permet d'isoler le nom d'un fichier si on connaît son chemin complet :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
Public Function NomFichier(Chemin As String) As String
Dim i As Integer
i = 1
Do
If Mid(Chemin, Len(Chemin) - i, 1) = "\" Then
NomFichier = Right(Chemin, i)
Exit Function
Else
i = i + 1
End If
Loop
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 ;
Veuillez décompresser l'archive dans un répertoire de votre choix.
Vos remarques et suggestions sont les bienvenues 48 commentaires
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.































