Introduction
Do you have a requirement to develop a Word automation application (Microsoft Office Automation), that will support in all the Word versions? What exactly may be the solution?
The answer is Late Binding. In C#, it is possible to implement late-binding using reflection and you can see its implementation in my previous article "Word Automation".
This article gives an idea about the implementation of late binding using dynamic
keyword in .NET Framework 4.0
Dynamic Keyword
This is a new keyword introduced in VS 2010 and this type will bypass static check at compile time. That is, an element that is typed as dynamic
is assumed to support any operation and the compiler will not give any error. But when executing, the run time will try to invoke the method/property using reflection and if it is found, then that will execute, otherwise will provide run time error. Refer to the MSDN link for getting a better idea about the dynamic
keyword.
Late Binding
In the case of early binding, we have to refer (add reference) to the Office Word object library directly to the project. The result is, only that type can be used inside the project and while running, that Word version should be installed in the target machine. But in the case of late binding, just create the Word application instance using the program id as shown below:
Type wordType = Type.GetTypeFromProgID("Word.Application");
dynamic wordApplication = Activator.CreateInstance(wordType);
This will give a Word application object with installed Word version. After that, all the properties, functions, etc. can be invoked using reflection. No need to add any office references statically in the project. See the image below:
Is it possible to support all Microsoft Word versions using a single implementation?
The answer is yes up to the current Word version (2010). How? Let us take one example. We have one API provided by Microsoft WordApplication.Documents.Open()
for opening a document to the document collection. You can see the syntax below, where all the parameters in bold letters are the same. They will add new parameters when changing one version to the next, by maintaining all the previous version's API parameters (order-wise also) same.
In our implementation, we will only provide the mandatory parameters for opening a document (represented using bold letters). In all the other APIs, the thing is the same. That is the secret for supporting multiple versions.
Office 2000
WordApplication.Documents.Open(FileName,ConfirmConversions,
ReadOnly,AddToRecentFiles,PasswordDocument, PasswordTemplate,Revert,
WritePasswordDocument, WritePasswordTemplate,Format,Encoding, Visible);
Office 2003
WordApplication.Documents.Open(FileName,ConfirmConversions,
ReadOnly,AddToRecentFiles, PasswordDocument, PasswordTemplate, Revert,
WritePasswordDocument,WritePasswordTempate,Format,Encoding, Visible,
OpenAndRepair, DocumentDirection,NoEncodingDialog, XMLTransform);
Office 2007
WordApplication.Documents.Open(FileName,ConfirmConversions,
ReadOnly,AddToRecentFiles,PasswordDocument,PasswordTemplate,Revert,
WritePasswordDocument,WritePasswordTemplate,Format,Encoding,Visible,
OpenAndRepair, DocumentDirection, NoEncodingDialog, XMLTransform);
Office 2010
WordApplication.Documents.Open(FileName,ConfirmConversions,ReadOnly,
AddToRecentFiles, PasswordDocument,PasswordTemplate,Revert,
WritePasswordDocument,WritePasswordTemplate,Format,Encoding,Visible,
OpenAndRepair,DocumentDirection,NoEncodingDialog, XMLTransform)
Using the Code
The below code samples give an idea about how to use dynamic
keyword for accessing methods/properties from any type.
Creating Word Application
dynamic _wordApplication = null;
dynamic _wordDoc = null;
public void CreateWordApplication()
{
string message = "Failed to create word application. Check whether"
+ " word installation is correct.";
Type wordType = Type.GetTypeFromProgID("Word.Application");
if (wordType == null)
{
throw new Exception(message);
}
_wordApplication = Activator.CreateInstance(wordType);
if (_wordApplication == null)
{
throw new Exception(message);
}
}
Opening Word Document
public void CreateWordDoc(object fileName, bool isReadonly)
{
if (File.Exists(fileName.ToString()) && _wordApplication != null)
{
object readOnly = isReadonly;
object isVisible = true;
object missing = System.Reflection.Missing.Value;
_wordDoc = _wordApplication.Documents.Open(fileName, missing,
isReadonly, missing, missing, missing,
missing, missing, missing, missing,
missing, isVisible);
}
}
Closing Word Document
public bool CloseWordDoc(bool canSaveChange)
{
bool isSuccess = false;
if (_wordDoc != null)
{
object saveChanges = null;
if (canSaveChange)
{
saveChanges = -1; }
else
{
saveChanges = 0; }
_wordDoc.Close(saveChanges);
_wordDoc = null;
isSuccess = true;
}
return isSuccess;
}
Closing Word Application
public bool CloseWordApp()
{
bool isSuccess = false;
if (_wordApplication != null)
{
object saveChanges = -1; _wordApplication.Quit(saveChanges);
_wordApplication = null;
isSuccess = true;
}
return isSuccess;
}
Getting a Word Count
public int GetWordCount(string word)
{
object wordDoc = _wordDoc;
int count = 0;
do
{
if (_wordDoc == null)
{
break;
}
if (word.Trim().Length == 0)
{
break;
}
_wordDoc.Activate();
dynamic content = _wordDoc.Content;
count += GetCountFromRange(_wordDoc.Content, word);
int rangeCount = _wordDoc.Comments.Count;
for(int i = 1; i <= rangeCount;)
{
count += GetCountFromRange(_wordDoc.Comments.Item(i), word);
break;
}
rangeCount = _wordDoc.Sections.Last.Headers.Count;
for (int i = 1; i <= rangeCount; i++)
{
count += GetCountFromRange(
_wordDoc.Sections.Last.Headers.Item(i).Range, word);
}
rangeCount = _wordDoc.Sections.Last.Footers.Count;
for (int i = 1; i <= rangeCount; i++)
{
count += GetCountFromRange(
_wordDoc.Sections.Last.Footers.Item(i).Range, word);
}
rangeCount = _wordDoc.Shapes.Count;
for (int i = 1; i <= rangeCount; i++)
{
dynamic textFrame = _wordDoc.Shapes.Item(i).TextFrame;
int hasText = textFrame.HasText;
if (hasText < 0)
{
count += GetCountFromRange(textFrame.TextRange, word);
}
}
}
while(false);
return count;
}
Getting Word Count from a Range
private int GetCountFromRange(dynamic range,string word)
{
int count = 0;
object missing = System.Reflection.Missing.Value;
object matchAllWord = true;
object item = 1; object whichItem = 1; _wordDoc.Goto(item, whichItem);
dynamic find = range.Find;
find.ClearFormatting();
find.Forward = true;
find.Text = word;
find.MatchWholeWord = true;
find.Execute();
bool found = find.Found;
while (found)
{
++count;
find.Execute();
found = find.Found;
}
return count;
}