Remerciements▲
Je remercie Piotrek (désolé, je ne connais que son pseudo) pour ses précieuses informations , J-M RABILLOUD pour son tutoriel, « Pilotage Microsoft OFFICE », qui m'a bien aidé, Thomas Lebrun et sjrd (même remarque que pour Piotrek) pour l'œil attentif qu'ils ont bien voulu prêter à ce document ainsi qu'à mon épouse que l'informatique n'amuse pas, mais qui a néanmoins relu ce document afin qu'il vous soit présenté dans les meilleures conditions.
I. Introduction▲
Ce document se veut un exposé pratique, mais non exhaustif des techniques à mettre en œuvre pour compléter un document Word depuis un programme C# en pilotant directement Word.
II. Préparation▲
Avant de passer à la programmation proprement dite, il y a lieu d'ajouter une référence à Word dans votre projet. La communication entre votre application et Word se fera au moyen de la technologie OLE Automation. Toutefois, ceci sera complètement pris en charge par les dll que nous allons référencer.
Dans Visual Studio : pour ajouter cette référence, allez dans Projet>Ajouter une référence>Onglet COM et sélectionnez Microsoft Word xx.x Object Library.
Dans SharpDevelop : Dans votre projet, cliquez avec le bouton droit sur Reference>Ajouter une référence, Onglet COM et sélectionnez Microsoft Word xx.x Object Library.
Dans mon cas, la version était 11.0.
Les dll Interop.VBIDE.dll et Interop.Word.dll sont maintenant ajoutées à votre projet.
III. Connecter et déconnecter votre application à Word▲
// connexion à Word
Microsoft.
Office.
Interop.
Word.
Application msWord =
new
Microsoft.
Office.
Interop.
Word.
Application
(
);
msWord.
Visible =
false
;
// mettez cette variable à true si vous souhaitez visualiser les opérations.
object
missing =
System.
Reflection.
Missing.
Value;
....
// Fermeture de word
msWord.
Quit
(
ref
missing,
ref
missing,
ref
missing);
Ce code comme tout le code qui suivra est finalement très simple. Vous pouvez toutefois voir deux points particuliers :
- la définition de l'objet missing. Celui-ci sera régulièrement utilisé pour remplacer les paramètres inutilisés ;
- la présence de ref. Les méthodes définies dans l'interface de Word s'attendent généralement à recevoir en paramètre des références à des objets.
IV. Ajouter et sauver un document sur base d'un modèle particulier▲
Nous allons maintenant créer un document sur base d'un modèle précédemment défini dans Word et se nommant « Mon Template » et le sauver sous le nom de « Mon nouveau document ».
Le template utilisé se trouve dans le répertoire des templates de Word et non avec l'application. Il vous est toujours possible de spécifier un chemin différent. De même, sans indication contraire le document est sauvé dans le répertoire spécifié dans Word.
Microsoft.
Office.
Interop.
Word.
Document nvDoc ;
// Choisir le template
object
templateName =
@"Mon Template.dot"
;
// Créer le document
nvDoc =
msWord.
Documents.
Add
(
ref
templateName,
ref
missing,
ref
missing,
ref
missing) ;
...
// Attribuer le nom
object
fileName =
@"Mon nouveau document.doc"
;
// Sauver le document
nvDoc.
SaveAs
(
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing) ;
// Fermer le document
nvDoc.
Close
(
ref
missing,
ref
missing,
ref
missing) ;
Si vous souhaitez pouvoir également réaliser des modifications dans un document précédemment créé, nous pouvons adapter le code comme suit :
// Attribuer le nom
object
fileName =
@"Mon nouveau document.doc"
;
Microsoft.
Office.
Interop.
Word.
Document nvDoc ;
// Tester s'il s'agit d'un nouveau document ou d'un document existant.
if
(
System.
IO.
File.
Exists
((
string
)fileName))
{
// ouvrir le document existant
nvDoc =
msWord.
Documents.
Open
(
ref
fileName,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing);
}
else
{
// Choisir le template
object
templateName =
@"Mon Template.dot"
;
// Créer le document
nvDoc =
msWord.
Documents.
Add
(
ref
templateName,
ref
missing,
ref
missing,
ref
missing) ;
}
...
// Sauver le document
nvDoc.
SaveAs
(
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing) ;
// Fermer le document
nvDoc.
Close
(
ref
missing,
ref
missing,
ref
missing) ;
Même dans le cas d'une modification du fichier, nous continuons à utiliser la méthode SaveAs.
V. Remplir des champs de formulaire▲
Pour ajouter automatiquement des informations à des endroits bien précis dans votre document, vous pouvez utiliser des champs de formulaire (FormFields). Si vous placez ces champs de formulaire dans une section protégée avec un mot de passe (protection en mode formulaire) et que vous décochez l'option « Fill in » des différents champs, une fois le document généré, l'utilisateur ne pourra pas modifier les données remplies automatiquement. De même, par programme vous pouvez ouvrir le document et mettre à jour les valeurs sans altérer le reste du document. Toutefois, vous constaterez que lorsque le document est ainsi protégé, un certain nombre de fonctionnalités sont retirées à l'utilisateur.
// Les champs de formulaire définis dans le modèle se nomment "Nom" et "Prenom".
object
field =
"Nom"
;
nvDoc.
FormFields.
get_Item
(
ref
field).
Result =
"Mon nom"
;
field =
"Prenom"
;
nvDoc.
FormFields.
get_Item
(
ref
field).
Result =
"Mon prénom"
;
Le champ de formulaire doit exister sinon c'est le plantage. C'est pourquoi il est conseillé de placer chaque chargement de champ dans un try/catch. Une autre solution consiste à utiliser la méthode « Exist » de la classe bookmarks. En effet à chaque champ de formulaire est associé un signet. Cette solution, bien que séduisante, est imparfaite, car si vous aviez toujours un signet associé à votre champ de formulaire, le signet pourrait exister sans qu'il soit associé à un champ de formulaire.
Si vous n'êtes pas un adepte du try/catch et que vous voulez une solution sans risque, vous pouvez charger tous les noms des champs de formulaire dans une hashtable après l'ouverture du document et ensuite vérifier la présence du nom dans la hashtable avant de remplir le champ. Pour récupérer les noms, il vous suffit d'utiliser la collection FormFields et la propriété Name de chaque Formfield.
Une alternative à l'utilisation des champs de formulaire est d'utiliser « Bookmark » et « Range ». Mais dans ce cas, il sera beaucoup plus difficile de mettre en œuvre la modification d'un document déjà généré (voir la technique utilisée dans « Transférer du texte formaté »).
Vous êtes maintenant capable de transférer du contenu dans votre document, mais il ne s'agit que de texte non formaté.
VI. Transférer du texte formaté▲
Aucune méthode ou propriété des objets de Word ne permet de transférer directement du texte au format rtf de votre application à Word. Nous allons devoir faire intervenir un élément supplémentaire, le clipboard.
Pour envoyer du contenu au clipboard, il suffit d'utiliser :
string
monContenu =
"Le texte que je transfère"
;
Clipboard.
SetDataObject
(
monContenu,
false
) ;
mais dans ce cas, le contenu est considéré comme du texte simple.
Nous allons devoir signaler au clipboard que le contenu est du type rtf. Nous disposons pour cela de la classe DataObject.
// Le texte que je transfère
string
monContenu =
@"{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl
{\f0\fswiss\fcharset0 Arial;}{\f1\fnil\fcharset0 Arial;}
{\f2\fmodern\fprq1\fcharset0 Courier;}}
{\colortbl ;\red0\green0\blue0;}
{\*\generator Msftedit 5.41.15.1503;}
\cf1\b\i\f2 Le texte que je transfère\cf0\b0\i0\f0}"
;
DataObject clipData =
new
DataObject
(
DataFormats.
Rtf,
monContenu) ;
Clipboard.
SetDataObject
(
clipData,
false
) ;
Le contenu est dans le clipboard. Il ne nous reste plus qu'à le rapatrier dans notre document. Pour l'exemple, j'ai placé un signet dans le modèle à l'endroit où doit être inséré mon texte. Il me suffit alors de faire :
field =
"TexteRtf"
;
nvDoc.
Bookmarks.
get_Item
(
ref
field).
Range.
Paste
(
);
Vous pouvez de la même manière, intégrer des images, du texte html et bien d'autres (voir DataFormats).
Attention si votre signet se trouve dans une section protégée, il sera nécessaire de déprotéger et de reprotéger le document.
field =
"TexteRtf"
;
object
pwd =
"LeMotDePasse"
;
nvDoc.
Unprotect
(
ref
pwd);
nvDoc.
Bookmarks.
get_Item
(
ref
field).
Range.
Paste
(
);
object
noReset =
true
;
nvDoc.
Protect2002
(
Microsoft.
Office.
Interop.
Word.
WdProtectionType.
wdAllowOnlyFormFields,
ref
noReset,
ref
pwd);
Malheureusement, seule la moitié de l'objectif est ainsi atteinte. En effet, s'il s'agit d'un nouveau document, alors tout est pour le mieux. Par contre, s'il s'agit d'un document préexistant, vous constaterez que le texte ne remplace pas l'ancien texte, mais se place devant.
Les différentes techniques que j'ai essayées, soit ne détruisaient pas le texte antérieur, soit détruisaient le signet. En définitive, la solution est d'utiliser non pas un, mais deux signets. Le texte devant être placé entre les deux signets.
// On déprotège le document
object
pwd =
"LeMotDePasse"
;
nvDoc.
Unprotect
(
ref
pwd);
// On identifie l'emplacement
field =
"TexteRtf"
;
object
fieldEnd =
"FinTexteRtf"
;
object
posStart =
nvDoc.
Bookmarks.
get_Item
(
ref
field).
Start;
object
posEnd =
nvDoc.
Bookmarks.
get_Item
(
ref
fieldEnd).
End;
nvDoc.
Range
(
ref
posStart,
ref
posEnd).
Select
(
);
// On transfert le texte
msWord.
Selection.
Paste
(
);
// On déplace le bookmark de fin
nvDoc.
Bookmarks.
Add
((
string
)fieldEnd,
ref
missing);
// On reprotège le document
object
noReset =
true
;
nvDoc.
protect2002
(
Microsoft.
Office.
Interop.
Word.
WdProtectionType.
wdAllowOnlyFormFields,
ref
noReset,
ref
pwd);
Il est nécessaire après la copie du texte de redéfinir le signet de fin sinon il reste en début de chaîne et lors de la prochaine modification, le texte existant ne sera pas correctement remplacé. Remarquez que nous n'avons plus utilisé la méthode Paste de la classe Range, mais bien celle de la classe Selection. En effet, la méthode paste ne déplace pas le curseur. Dès lors, il nous est impossible de replacer correctement le signet. Par contre en passant par la méthode Select, le curseur est correctement positionné en fin de chaîne.
Notez également que la méthode paste s'applique sur Selection qui est directement une propriété de l'objet msWord (type Application) bien que cette sélection ait été réalisée sur l'object nvDoc (type Document).
Vous voilà prêt pour communiquer avec Word. J'espère que ce bref aperçu pratique vous facilitera la tâche et n'hésitez pas à poser de questions sur le forum DotNet de développer : https://www.developpez.net/forums/viewforum.php?f=49
VII. Le code complet▲
// Connexion à Word
Microsoft.
Office.
Interop.
Word.
Application msWord =
new
Microsoft.
Office.
Interop.
Word.
Application
(
);
msWord.
Visible =
false
;
// mettez cette variable à true si vous souhaitez visualiser les opérations.
object
missing =
System.
Reflection.
Missing.
Value;
// Attribuer le nom
object
fileName =
@"Mon nouveau document.doc"
;
Microsoft.
Office.
Interop.
Word.
Document nvDoc ;
// Tester s'il s'agit d'un nouveau document ou d'un document existant.
if
(
System.
IO.
File.
Exists
((
string
)fileName))
{
// ouvrir le document existant
nvDoc =
msWord.
Documents.
Open
(
ref
fileName,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing);
}
else
{
// Choisir le template
object
templateName =
@"Mon Template.dot"
;
// Créer le document
nvDoc =
msWord.
Documents.
Add
(
ref
templateName,
ref
missing,
ref
missing,
ref
missing) ;
}
// Les champs de formulaire définis dans le modèle se nomment "Nom" et "Prenom".
object
field =
"Nom"
;
nvDoc.
FormFields.
get_Item
(
ref
field).
Result =
"Mon nom"
;
field =
"Prenom"
;
nvDoc.
FormFields.
get_Item
(
ref
field).
Result =
"Mon prénom"
;
// Copie Le texte que je transfère dans le clipboard
string
monContenu =
@"{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl
{\f0\fswiss\fcharset0 Arial;}{\f1\fnil\fcharset0 Arial;}
{\f2\fmodern\fprq1\fcharset0 Courier;}}
{\colortbl ;\red0\green0\blue0;}
{\*\generator Msftedit 5.41.15.1503;}
\cf1\b\i\f2 Le texte que je transfère\cf0\b0\i0\f0}"
;
DataObject clipData =
new
DataObject
(
DataFormats.
Rtf,
monContenu) ;
Clipboard.
SetDataObject
(
clipData,
false
) ;
// Transfert le clipboard dans Word
// On déprotège le document
object
pwd =
"LeMotDePasse"
;
nvDoc.
Unprotect
(
ref
pwd);
// On identifie l'emplacement
field =
"TexteRtf"
;
object
fieldEnd =
"FinTexteRtf"
;
object
posStart =
nvDoc.
Bookmarks.
get_Item
(
ref
field).
Start;
object
posEnd =
nvDoc.
Bookmarks.
get_Item
(
ref
fieldEnd).
End;
nvDoc.
Range
(
ref
posStart,
ref
posEnd).
Select
(
);
// On transfert le texte
msWord.
Selection.
Paste
(
);
// On déplace le bookmark de fin
nvDoc.
Bookmarks.
Add
((
string
)fieldEnd,
ref
missing);
// On reprotège le document
object
noReset =
true
;
nvDoc.
Protect2002
(
Microsoft.
Office.
Interop.
Word.
WdProtectionType.
wdAllowOnlyFormFields,
ref
noReset,
ref
pwd);
// Sauver le document
nvDoc.
SaveAs
(
ref
fileName,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing,
ref
missing) ;
// Fermer le document
nvDoc.
Close
(
ref
missing,
ref
missing,
ref
missing) ;
// Fermeture de word
msWord.
Quit
(
ref
missing,
ref
missing,
ref
missing);