Générer un document Microsoft Word (C#)

Jean-Alain Baeyens

Comment créer, modifier un document Word depuis une application C# et ce y compris le transfert d'informations au format Rtf.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Générer un document Microsoft Word (C#)

- Auteur : Jean-Alain Baeyens

- Parution : 12 novembre 2004

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

Sommaire

- Remerciements

- I. Introduction

- II. Préparation

- III. Connecter et déconnecter votre application à Word

- IV. Ajouter et sauver un document sur base d'un modèle particulier

- V. Remplir des champs de formulaire

- VI. Transférer du texte formaté

- VII. Le code complet

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