Mit C# und Word Serienbriefe erstellen

Wie dem einen oder anderen ja vielleicht bekannt ist, kann man die ganzen Office-Anwendungen von Microsoft ja aus vielen Programmiersprachen fernsteuern. Dazu gibt es eine COM-Schnittstelle (leider :() Die ist zwar nicht gerade modern oder praktisch, aber sie funktioniert relativ gut.

Theoretisch geht das auch mit .NET 2.0, praktisch gesehen möchte man es aber erst mit .NET 4.0 probieren: Die Funktionen von Office nehmen oft wahnsinnig viele Parameter (15 sind da keine Seltenheit), und vor .NET 4.0 gab es keine optionalen Parameter, daher musste man für alles, was man nicht benutzen wollte Type.Missing angeben. Das war natürlich extrem unpraktisch. Mit dem neuen .NET ist Type.Missing jetzt der Standard-Wert der optionalen Parameter, und das ganze ist deutlich benutzbarer geworden.

Ich hatte mir zu Ziel gesetzt einen kleinen Serienbrief-Generator in C# zu schreiben. Ich weiß, Word hat so eine Funktion schon eingebaut, aber die Daten für die Briefe sollten automatisch generiert werden, und es hat mich auch einfach interessiert 🙂 Die Briefe basieren auf einem Template, was einfach ein normales Word-Dokument ist, das Platzhalter enthält. (Die Platzhalter sind auch einfach nur Text, keine Magie dahinter…) Diese Platzhalter werden durch ein einfaches Suchen&Ersetzen umgewandelt, und danach wird das Dokument ausgedruckt. Funktioniert auch recht gut, aber es gibt ein paar seltsame Dinge, auf die ich kurz eingehen will:

Zunächst legen wir ein neues C# (oder anderes .NET) Projekt an, danach wird ein Verweise auf die Word-COM-Schnittstelle hinzugefügt:

word_refIn dem Fenster (das ist VisualStudio 2012, anderen sollten ähnlich sein), klickt man zunächst auf COM, und sucht dann am besten nach „word“, danach setzt man den Haken, den man im Bild sehen kann, und klick dann(!) auf OK. Wenn man nur auf OK klickt, ohne den Haken zu setzen, passiert gar nichts :). Nun wird das Word-Interop Assembly und noch ein paar mehr referenziert.

Nun kann es mit dem Code losgehen: Zunächst ein using:


using Word = Microsoft.Office.Interop.Word;

Und dann der Code, der Word steuert:


var word = new Word.Application();
#if DEBUG
word.Visible = true;
#endif
word.Documents.Open(Path.Combine(Environment.CurrentDirectory, "Templates\\Brief.doc"));
word.Selection.Find.Execute("{DATE}", ReplaceWith:  DateTime.Now.ToShortDateString());
word.Selection.Move();
word.Selection.Find.Execute("{GREETING}", ReplaceWith: "Sehr geehrte" + (currentAddress.IsMale ? "r Herr" : " Frau"));
word.Selection.Move();
word.Selection.Find.Execute("{NAME}", ReplaceWith: currentAddress.LastName);
word.Selection.Move();
int copies = (cb_Copy.IsChecked.HasValue && cb_Copy.IsChecked.Value) ? 2 : 1;
for (int i = 0; i < copies; i++)
word.PrintOut();
System.Threading.Thread.Sleep(2000); //Quiting to fast seem to cancel printing...
word.Quit(SaveChanges: false);

Wir legen erst ein neues Word-Objekt an, und machen das Fenster auch sichtbar, zumindest im Debug-Modus (ansonsten bleibt das Fenster versteckt, ist auch nicht schlecht), danach geht es mit den eigentlichen Word-Befehlen los. Hier muss man beachten, dass sich diese sehr nah an dem halten, was man in der Oberfläche machen kann.


word.Selection.Find.Execute("{GREETING}", ReplaceWith: "Sehr geehrte" + (currentAddress.IsMale ? "r Herr" : " Frau"));
word.Selection.Move();

Mit der ersten Zeile wird der Platzhalter „{GREETING}“ durch eine Anrede ersetzt, aber danach ist der ersetzte Text markiert. Daher müssen wir den Cursor ans Ende der Auswahl verschieben, und dadurch die Auswahl aufheben, weil sonst das nächste Find nur auf die Auswahl wirkt. Genauso muss man die Platzhalter in der richtigen Reihenfolge ersetzen, weil die Suchfunktion nicht das komplette Dokument durchsucht, sondern nur ab Cursor-Position. Das kann man vermutlich noch mit Parametern (die direkt die Checkboxen in der Word-Oberfläche nachbilden) umstellen, aber so geht es auch.

Am besten überlegt man sich zunächst, wie man die Aufgabe in der Oberfläche lösen würde, und setzt das dann direkt in Code um.

int copies = (cb_Copy.IsChecked.HasValue && cb_Copy.IsChecked.Value) ? 2 : 1;
for (int i = 0; i < copies; i++)
word.PrintOut();
System.Threading.Thread.Sleep(2000); //Quiting to fast seem to cancel printing...
word.Quit(SaveChanges: false);

Hier wird das Dokument ausgedruckt. Eigentlich kann man mit dem Parameter „Copies“ die Anzahl der Kopien angeben, aber bei mir wurde immer nur eine ausgedruckt, egal, wie viele ich eingestellt habe… Daher diese Schleife. Bevor man nun word.Quit() aufruft, sollte man noch einen Moment warten, ansonsten kann es sein, dass der letzte Druckauftrag nicht mehr abgeschickt wird.

Noch ein kleiner Tipp: Word scheint es ziemlich über zu nehmen, wenn man das Programm mit dem Debugger unterbricht, danach wollte bei mir teilweise gar nichts mehr gehen. Generell wirkt alles etwas instabil, und viele der Parameter sind auch nicht wirklich klar in ihrer Funktion. Generell kann man aber immer fast alle Parameter weglassen, viele sind wirklich nur für Spezialfälle, von denen Word ja doch mehr behandeln kann, als man oft so denkt.

Wo wir gerade bei komischem Verhalten sind: Das normale \r\n scheint Word in „Enter+Leerzeichen“ umzuwandeln, ein reines \n wird scheinbar ignoriert, und nur ein reines \r produziert eine neue Zeile. Das verstehe mal jemand. Ganz ehrlich: Ich will der Code von Word echt nicht sehen…

Fazit: Man kann Word (und auch die anderen Office-Produkte) komplett aus .NET steuern. Das finde ich wirklich bemerkenswert. Leider ist die API eine mittlere Katastrophe, und scheint auch einiges an Merkwürdigkeiten zu beinhalten. Dennoch reicht es für solche einfachen Sachen problemlos aus, und es ist wirklich interessant anzusehen, wie sich ein Word-Dokument plötzlich von selber im Hintergrund schreibt 🙂

2 Gedanken zu „Mit C# und Word Serienbriefe erstellen

  1. Finde es bis jetzt klasse . Verstehe aber,nur die hälfte .Das mit dem Code verstehe ich 0 gehe in die 10 jetzt und ja ^^ kannst du mir das Fertig zuschicken per Mail ? Denn Code

    1. Hi Dustin,
      nein, den fertigen Code kann ich dir leider nicht schicken, das ist ja nur ein Teil aus einem kleinen Tool das ich mir mal geschrieben hatte. Was hast du denn vor? Vielleicht lässt sich das einfacher mit der „normalen“ Serienbrief-Funktion machen, die in Word eingebaut ist, oder einem Word-Marko…

      -Niklas

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert