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
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 :
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
Sub
que 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
Sub
IV-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
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 :
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.