Developpez.com

Plus de 14 000 cours et tutoriels en informatique professionnelle à consulter, à télécharger ou à visionner en vidéo.

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

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 34 commentaires Donner une note à l'article (5) 

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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

Image non disponible

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 :

Image non disponible

Des boutons permettent de choisir le document type :

Image non disponible

Par exemple, celui-ci :

Image non disponible

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

Image non disponible

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

Image non disponible

II-B. Les références utiles

Image non disponible

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

Image non disponible

III-A. Choisir un patient 

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

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

Image non disponible

III-B. Voir le dossier du patient

Le clic déclenche ce code :

 
Sélectionnez
1.
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 :

Image non disponible

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é ».

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

Image non disponible

Trois variables sont définies au niveau formulaire :

 
Sélectionnez
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é  
C:\MesDocuments\PrintScreen\i586.jpg

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.

Image non disponible

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 :

 
Sélectionnez
Private Sub btCptesRendus_Click()
  Call ChercherDocType
End Sub

que voici :

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

C:\MesDocuments\PrintScreen\i587.jpg

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

Image non disponible

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 :

Image non disponible

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

les tables en jeu :

Image non disponible

et le SQL :

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

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

C:\MesDocuments\PrintScreen\i588.jpg
 
Sélectionnez
1.
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.

C:\MesDocuments\PrintScreen\i570.jpg nous pouvons maintenant appeler Word pour faire la mise au net du document personnalisé.

C:\MesDocuments\PrintScreen\i589.jpg

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…)

Image non disponible

Une nouvelle fenêtre apparaît :

Image non disponible

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) :

Image non disponible

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

Image non disponible

Remarquez : les dates au format américain !

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

Image non disponible

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

Image non disponible

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 :

Image non disponible

dans le but d'obtenir ce résultat :

Image non disponible

Vous pouvez aussi utiliser un raccourci : <CTRL + F9> qui insérera la balise Image non disponible 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 : Image non disponible
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 : Image non disponible

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

Image non disponible
Image non disponible

V-A-2. Le traitement dans Access

Pour rappel, nous sommes ici :

C:\MesDocuments\PrintScreen\i589.jpg

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.

 
Sélectionnez
1.
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 :

Image non disponible

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

Image non disponible

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

Image non disponible

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 » :

Image non disponible

Cette fois le texte prend cet aspect :

Image non disponible

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 :

C:\MesDocuments\PrintScreen\i589.jpg

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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 bkm va 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 :

Image non disponible

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 :

C:\MesDocuments\PrintScreen\i589.jpg

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.

 
Sélectionnez
1.
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 :

Image non disponible

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 :

 
Sélectionnez
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é

C:\MesDocuments\PrintScreen\i590.jpg
 
Sélectionnez
Private Sub OuvrirDocPerso()
  Shell "C:\WINDOWS\EXPLORER.EXE " & CheminDocPerso
End Sub

L'instruction Shell "C:\WINDOWS\EXPLORER.EXE " & LeCheminDunFichier est 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) :

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

 
Sélectionnez
1.
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 ;

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 34 commentaires Donner une note à l'article (5) 

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.

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

  

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.