C# Integratie met Microsoft Word
Microsoft Word is te integreren in elke applicatie met behulp van het Word-objectmodel. In dit artikel integreren we events uit Word met een .NET applicatie. Ik zal een voorbeeldcase uitwerken, waarbij een Word-document, nadat deze is bijgewerkt met waarden uit een XML-bestand, in een message queue wordt gestopt, van waaruit deze verder verwerkt kan worden in een archiverings- of printer-omgeving.
Visual Studio.NET zal nu de Interop assembly maken
Om gebruik te maken van het Word-objectmodel dienen we de Microsoft Word 11 Object Library reference toe te voegen aan ons project. Visual Studio.NET zal nu de Interop assembly maken. Interop assembly’s zijn componenten die door het .NET framework tijdens compileren gebruikt worden om functionaliteit uit COM componenten te gebruiken. Tijdens runtime zorgt de assembly voor het ver- en uitpakken van managed waarden van en naar de COM-component toe (marshalling).
Word 2003
Het Word 2003 object model ziet er als volgt uit:

Het Word 2003 objectmodel
We hebben het Application-object en die kan een Document bevatten. Op zowel application- als document-niveau is er een aantal events beschikbaar. Een document bevat verder nog een collectie van bookmarks waarbinnen de bladwijzers die door de gebruiker in het bestand zijn gedefinieerd, benaderd kunnen worden.
Om te beginnen starten we de Word-applicatie. Dit kan door de volgende code uit te voeren:
Word.ApplicationClass wordApp =
new Word.ApplicationClass();
wordApp.Visible = true;
Wanneer deze code wordt uitgevoerd, zal Microsoft Word starten zonder document. De gebruiker zal nu zelf een nieuw document moeten aanmaken of een bestaand document moeten selecteren.
Events
Het Word Application-object bevat een aantal events om acties die door de gebruiker worden geïnitieerd, terug te koppelen naar de applicatie. Om hiervan gebruik te maken moeten we een aantal eventlistener-methoden maken. Deze moeten we vervolgens registreren met behulp van eventhandlers die reageren op events die door Word worden verstuurd.
//event handler notifying that a new document is created
myNewDoc = new
Word.ApplicationEvents4_NewDocumentEventHandler
(DocNew);
wordApp.ApplicationEvents4_Event_NewDocument +=
myNewDoc;
//event handler notifying that a document has been
// opened
myOpenDoc = new
Word.ApplicationEvents4_DocumentOpenEventHandler
(MyOpenEventHandler);
wordApp.DocumentOpen += myOpenDoc;
//event handler notifying that the user is about to
//save the document
mySaver = new
Word.ApplicationEvents3_DocumentBeforeSaveEventHandler
(beforeSave);
wordApp.ApplicationEvents3_Event_DocumentBeforeSave +=
mySaver;
Dit zijn voorbeelden van events die op kunnen treden, wanneer de gebruiker een document creëert, opent of wil opslaan. Word heeft naast deze drie events ook nog events met betrekking tot resizen, dubbelklikken, mail-mergen, (de-)activeren, etc.
Wanneer de gebruiker beslist zijn document op te slaan, kunnen we dat doen en het b.v. sturen naar een message queue van waaruit ze gearchiveerd of afgedrukt kunnen worden.
In de volgende stap gaan we een bestaand Word-document openen in de Word-applicatie die we starten. De Documents-classproperty van de Word-application heeft een methode Open. Deze methode heeft in totaal 16 parameters, waarvan de 1e het belangrijkste in het rijtje is. Dit is namelijk het pad naar het document dat we willen openen.
//Here is the way to handle parameters you don’t know
//the value of in .NET
object missing = System.Reflection.Missing.Value;
object readOnly = false;
object isVisible = true;
object fileName = openFileDialog1.FileName;
//Open the document that was selected by the user
Word.Document wordDocument = wordApp.Documents.Open(
ref fileName, // Name of the document (including path)
ref missing, // true|false: Display the Convert File
// dialog box if the file isn’t in
// Microsoft Word format
ref readOnly, // true|false: Open document as
// read-only. Does not override
// read-only setting in Document.
ref missing, // true|false: Add to recently
// opened files
ref missing, // string: password to open document
ref missing, // string: password of template
ref missing, // true|false: revert changes in open
// document
ref missing, // string: password for saving document
ref missing, // string: password for saving template
ref missing, // string: formatter to open document.
// Default is "wdOpenFormatAuto"
ref missing, // string (MsoEncoding): document
// encoding (code-page or character-set)
// to view the document
ref isVisible,// true|false: Open document in a visible
// window
ref missing, // true|false: Open and repair document
// to prevent corruption
ref missing, // string (wdLeftToRight or
// wdRightToLeft): controls horizontal
// flow of the text
ref missing, // true|false: skip encoding dialog if
// Word doesn't recognize the encoding
ref missing); // xmlTransform
//Activate the document
wordDocument.Activate();
Nu zal niet alleen de Word-applicatie gestart zijn, maar deze zal ook starten met het document waarvan we de locatie hebben meegegeven.
Bookmarks
Binnen een Word-document is het mogelijk om gegevens van buitenaf te wijzigen door het gebruik van zgn. ‘bookmarks’ (bladwijzers). Deze bookmarks zijn door de gebruiker aangemaakt en op de juiste plekken in het document gepositioneerd.
Met bookmarks is het mogelijk om gegevens van buitenaf te wijzigen
Hieronder is een kopie van het document gemaakt zoals gebruikt is voor dit artikel.

We gaan binnen ons integratieproject data vanuit een XML-bestand invoegen in het Word-document. Daar hebben we natuurlijk een XML-bronbestand voor nodig. Dit XML-bestand heeft de volgende inhoud:
Ersin Çesmeli
Visual Studio.NET 2003
ecesmeli@bergler.nl
Wat we willen bereiken is dat de data uit het XML bestand op de plek van de bookmarks in Word geplaatst wordt. In onderstaand stukje voorbeeldcode is zichtbaar hoe je op een simpele manier aan de bladwijzers een waarde kunt toekennen. Het gebruik van identieke namen vergemakkelijkt het opbouwen van de xPath-query.
XmlDocument doc = new XmlDocument();
string executingDirectory = Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().
CodeBase).Replace(@"file:\", "");
string xmlDirectory =
Path.Combine(executingDirectory, @"..\..\test.xml");
doc.Load(xmlDirectory);
System.Xml.XmlNamespaceManager manager =
new System.Xml.XmlNamespaceManager (doc.NameTable);
manager.AddNamespace(
"ns", doc.DocumentElement.NamespaceURI);
// Enumerate through bookmarks added to the document
foreach(Word.Bookmark bookmark in
wordDocument.Bookmarks)
{
// In word bookmarks can’t have spaces.
// Instead the ‘_’ character is used.
// Replace _ with /ns:
string xpath = bookmark.Name.Replace("_", "/ns:");
// if name didn’t start with ‘_’ still add //ns:
if (xpath[0] != '/')
{
xpath = "//ns:" + xpath;
}
// Execute the xpath query using the namespace manager
System.Xml.XmlNode node =
doc.SelectSingleNode(xpath, manager);
if (node != null)
{
// Assing the value of the node to the bookmark
bookmark.Range.Text = node.InnerText;
}
}
Wanneer deze code wordt uitgevoerd, ziet ons document er als volgt uit:

Message Queue
We hebben nu bereikt dat de data uit ons XML-bestand in het Word-document terecht is gekomen. Wat doen we met dit document? We zouden het op een harde schijf kunnen plaatsen, maar bij een project dat ik voor een klant heb gedaan moest het document na generatie opgeslagen worden in een document management systeem. Om dit systeem te benaderen moet het document in een Microsoft Message Queue (MSMQ) geplaatst worden. Het document management systeem ‘luistert’ naar deze queue en pakt het document op om het vervolgens te archiveren.
In het volgende voorbeeld staat de eventhandler zoals deze is toegekend aan het ‘before save’ event. Dit event stuurt een aantal argumenten mee. Het eerste argument is het document dat de gebruiker wil bewaren. Het tweede is een indicator die aangeeft of de gebruiker de ‘Opslaan als’ optie heeft gebruikt. De derde indicator is heeft initieel de waarde false. Wanneer deze binnen de methode op true wordt gezet, zal Word het document niet opslaan. Het is een parameter waarmee het opslaan van een document door Word zelf kan worden geannuleerd.
Binnen de methode beforeSave wordt van het document een kopie gemaakt in de map C:\DOCS. De saveAs methode heeft 16 parameters, waarvan de eerste het pad aangeeft waar het document dient te worden bewaard. Doordat er een kopie van het document is gemaakt, kunnen we het bestand fysiek benaderen. Het originele document is immers in gebruik door Word.
public void beforeSave(
Word.Document doc,
ref bool saveAsUI,
ref bool cancelled)
{
//document is ready...
//saving a copy of document using the saveas option
if(saveAsUI)
{
object path = @"C:\DOCS\" + doc.Name;
//activate the file system watcher. See next chapter
fsw.EnableRaisingEvents = true;
doc.SaveAs(
ref path, //filename
ref missing, //fileFormat
ref missing, //lockComments
ref missing, //password
ref missing, //addToRecentFiles
ref missing, //writePassword
ref missing, //readOnlyRecommended
ref missing, //embedTrueTypeFonts
ref missing, //saveNativePictureFormat
ref missing, //saveFormsData
ref missing, //saveAsAOCELetter
ref missing, //encoding
ref missing, //insertLineBreaks
ref missing, //allowSubstitutions
ref missing, //lineEnding
ref missing //addBiDiMarks (control characters
// for bi-directional layout preservation)
);
}
}
FileSystemWatcher
Bij het ontwikkelen van de applicatie lopen we tegen het feit dat de SaveAs methode van het document een asynchrone methode is. Dit houdt in dat het niet mogelijk is om sequentieel na de aanroep verdere bewerkingen op het bestand te doen, omdat deze nog gelocked is door het proces. Om dit probleem te omzeilen kunnen we de FileSystemWatcher gebruiken. De FileSystemWatcher is een class, waarmee activiteiten op het bestandssysteem gemonitord kunnen worden. In de vorige methode hebben we een kopie van het document dat de gebruiker wilde bewaren, opgeslagen in de map C:\DOCS. Met behulp van een file system watcher kunnen we op een bepaalde locatie wijzigingen in bestanden monitoren om dat als startpunt te definiëren voor verdere bewerkingen. Binnen de startup van onze applicatie hebben we hiervoor een filesystemwatcher object gecreëerd. Deze class zit in de System.IO namespace.
//Create a filesystemwatcher for directory C:\DOCS
//who monitors files with extention '.doc'
fsw = new FileSystemWatcher(@"c:\docs", "*.doc");
//disable the filesystemwatch initially
fsw.EnableRaisingEvents = false;
//set the filter of events we are interested in
fsw.NotifyFilter = NotifyFilters.LastWrite;
//create our eventhandler
fsw.Changed += new FileSystemEventHandler(fsw_Changed);
Ook de file system watcher heeft een aantal events. Deze kunnen het verwijderen, hernoemen of wijzigen (inclusief creëren) van bestanden aankondigen. Voor dit artikel is alleen het Changed event interessant. De event handler zorgt ervoor dat een bestand wordt ingelezen in een byte-array. Deze wordt vervolgens verstuurd naar een message queue. Feitelijk zouden we nu ook het bestand, bijvoorbeeld als een BLOB, in een database kunnen bewaren of kopiëren naar een file share op het netwerk. Het idee is dat de bestanden uit de message queue opgepakt kunnen worden door bijvoorbeeld een document management pakket, waarvan vele variaties op markt beschikbaar zijn.
private void fsw_Changed(
object sender, FileSystemEventArgs e)
{
//This method is called by the filesystem watcher
FileStream fs =
new FileStream(e.FullPath, FileMode.Open);
byte[] data = new byte[fs.Length];
fs.Read (data, 0, data.Length);
fs.Close();
System.Messaging.MessageQueue msMq = new
System.Messaging.MessageQueue(
@".\Private$\wordQueue");
msMq.Send(data);
//put the file system watcher to idle mode
fsw.EnableRaisingEvents = false;
}
Conclusie
In dit artikel heb ik aangetoond dat Word op een betrekkelijk simpele manier kan worden geïntegreerd in een .NET applicatie. Microsoft levert een aparte toolset onder de noemer Visual Studio Tools for the Microsoft Office System, maar dit artikel toont aan dat je prima kunt integreren zonder deze aparte toolset.
Het enige waar ik tegen aanliep was dat de classes en events die er zijn, niet voldoende gedocumenteerd zijn en dat een enkele keer de documentatie die er is, niet up-to-date is. Het concept bladwijzers maakt het bijwerken van gegevens in een document vanuit een externe bron eenvoudiger, mede omdat deze binnen een applicatie geïtereerd kunnen worden, waarbij eigenschappen zoals de naam uitleesbaar zijn en de waardes van properties gevuld kunnen worden.
Sources
De sources die bij dit artikel horen kunt u downloaden via Cesmeli_WordIintegratie_SRC.zip.