Late Binding C# with MS Word 97 and up
-
Wednesday, July 21, 2010 10:40 PM
First off, I dont know if I am putting this in the right forum, so please, if it is not, let me know which is correct and or just move it to the correct one if possible. With that said...
I have have spent the day trying to understand late binding and trying to find some code examples. Since I do not understand it at all at this point, I thought I would ask here. Currently, I am using early binding to open a Word Template, mergerFields, and Save the document. However, I have no clue how to convert this code to use late binding and allow for it to use Word 97 (or if this is not posible at least 2003) up to the current version. If someone could please help me either convert the code, OR at least point me in the right direction, I would much appreciate it.
Thanks,
This code I was able to get help with online and here it is...
Object oMissing = System.Reflection.Missing.Value;
object missing = System.Reflection.Missing.Value;
Object oTrue = true;
Object oFalse = false;
Word.Application oWord = new Word.Application();
Word.Document oWordDoc = new Word.Document();
oWord.Visible = true;
DirectoryInfo di = new DirectoryInfo(Defaults.getVal("ExportFile", "Reports"));
if (di.Exists == false)
di.Create();
Object oTemplatePath = @"C:\AHAH\Gleaning Site Profile.dot";
Object saveAs = Defaults.getVal("GleaningSiteProfile", "Reports") + "_" + listView1.FocusedItem.Text
+ "__" + DateTime.Now.Month.ToString() + "-" + DateTime.Now.Day.ToString() + "-"
+ DateTime.Now.Year.ToString() + ".doc";
oWordDoc = oWord.Documents.Add(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing);
foreach (Word.Field myMergeField in oWordDoc.Fields)
{
iTotalFields++;
Word.Range rngFieldCode = myMergeField.Code;
String fieldText = rngFieldCode.Text;
if (fieldText.StartsWith(" MERGEFIELD"))
{
Int32 endMerge = fieldText.IndexOf("\\");
Int32 fieldNameLength = fieldText.Length - endMerge;
String fieldName = fieldText.Substring(11, endMerge - 11);
fieldName = fieldName.Trim();
if (fieldName == "UID")
{
myMergeField.Select();
myMergeField.Result.Font.Size = (float)20.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<int>("UID").ToString();
//oWord.Selection.TypeText();
}
if (fieldName == "ContactName")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("ContactName").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("ContactName");
//oWord.Selection.TypeText(reportDSet.Tables[0].Rows[0].Field<string>("ContactName"));
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "Contact Name Not On File For This Donor ";
//oWord.Selection.TypeText("No Contact Name not on File For This Donor");
}
}
if (fieldName == "DonorName")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("DonorName").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
//myMergeField.Result.Font.Bold = 1;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("DonorName");
//oWord.Selection.TypeText(reportDSet.Tables[0].Rows[0].Field<string>("DonorName"));
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "Donor Name Not On File ";
//oWord.Selection.TypeText("No Gleaning Date Not On File ");
}
}
if (fieldName == "FieldAddress")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("FieldAddress").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("FieldAddress");
// oWord.Selection.TypeText(
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "Address Of Event Not On File ";
//oWord.Selection.TypeText("No Gleaning Date Not On File ");
}
}
if (fieldName == "FieldCity")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("FieldCity").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("FieldCity");
// oWord.Selection.TypeText();
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "City Not On File ";
//oWord.Selection.TypeText("No Gleaning Date Not On File ");
}
}
if (fieldName == "FieldZipcode")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("FieldZipcode").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("FieldZipcode");
//oWord.Selection.TypeText();
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "Zipcode Not On File ";
//oWord.Selection.TypeText("No Gleaning Date Not On File ");
}
}
if (fieldName == "ContactPhone1")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("ContactPhone1").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("ContactPhone1");
//oWord.Selection.TypeText();
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "Contact Number Not On File ";
//oWord.Selection.TypeText("No Gleaning Date Not On File ");
}
}
if (fieldName == "ContactPhone2")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("ContactPhone2").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("ContactPhone2");
//oWord.Selection.TypeText();
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "Cell Number Not On File ";
//oWord.Selection.TypeText("No Gleaning Date Not On File ");
}
}
if (fieldName == "DayOfWeek")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("DayOfWeek").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("DayOfWeek");
// oWord.Selection.TypeText();
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = " ";
//oWord.Selection.TypeText(" ");
}
}
if (fieldName == "GleaningDate")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<DateTime>("GleaningDate").ToString().Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<DateTime>("GleaningDate").ToString();
//oWord.Selection.TypeText();
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "No Gleaning Date Not On File ";
//oWord.Selection.TypeText("No Gleaning Date Not On File ");
}
}
if (fieldName == "VolunteerNotes")
{
myMergeField.Select();
if (reportDSet.Tables[0].Rows[0].Field<string>("VolunteerNotes").Trim() != "")
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = reportDSet.Tables[0].Rows[0].Field<string>("VolunteerNotes");
// oWord.Selection.TypeText(If I end up using this instead you put the DataSet.tables..... here);
}
else
{
myMergeField.Result.Font.Size = (float)16.0;
myMergeField.Result.Text = "Volunteer Notes Not On File ";
//oWord.Selection.TypeText("No Gleaning Date Not On File ");
}
}
}
}
oWordDoc.SaveAs(ref 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);
}
}
else
{
MessageBox.Show("Please Select An Event and Try Creating The Report Again");
}
All Replies
-
Thursday, July 22, 2010 7:41 AMModerator
Hi Stizz
Late Binding in C# (unless you're using Net Framework 4.0) means you have to work with GetType().InvokeMember on each call to the Office application. It's a lot of work, especially if you're not intimately famliar with the object model, because there's no Intellisense. Starting with a bit of complete code, as you show above, is good, as it gives you the required syntax. I suggest, however, that you start with something shorter and simpler in order to get the hang of it. I'm not going to "translate" entire code methods for you - I get paid for that kind of thing :-)
Note: don't trust the code you've pasted 100%. For example, the following line is not valid and will not work:
Word.Document oWordDoc = new Word.Document(); You can't use the new keyword with anything but the Application object. You need to use wdApp.Documents.Add(//parameters here) to add a document to the application window.The first thing you need to do is decide on how you're going to "get hold" of the Word application. If you use late-binding, only, then you can't use the "new" keyword. Here's a blog article that goes through the various options available to you.
When using late binding, all your variables must be declared as the data type: object. When you need to pass parameters, these need to be in a colleciton of type: object[]
Here's a snippet to show you how to get a new Word document:
object wordDoc = null;
wordDoc = wdApp.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, wdApp, null);If you need to read or set a property, use BindingFlags.InvokeProperty. Parameters are passed in the last argument. If there are none, pass null (as above).
If you search the term InvokeMember on MSDN, and in the VSTO forum, especially, you'll turn up a number of examples you learn from.
Cindy Meister, VSTO/Word MVP- Marked As Answer by Bessie ZhaoModerator Thursday, July 29, 2010 7:04 AM
- Unmarked As Answer by Cindy Meister MVPMVP, Moderator Friday, July 30, 2010 8:19 AM
-
Thursday, July 29, 2010 6:38 PM
I understand that what you need to do to get the appropriate type and the use of objects and stuff. The part that I have no clue how to do is the MergeField.
Currently I have
Object objClassType;
Type oWord = Type.GetTypeFromProgID("Word.Application");
object[] parameter = new object[1];
objClassType = Activator.CreateInstance(oWord);
parameter[0] = true;
Object oWordDoc = oWord.InvokeMember("Documents", System.Reflection.BindingFlags.GetProperty, null, objClassType, null);
RuntimeTypeHandle handle = Type.GetTypeHandle(oWordDoc);
Type Docs = Type.GetTypeFromHandle(handle);
object doc = Docs.InvokeMember("Open", System.Reflection.BindingFlags.InvokeMethod,
null, oWordDoc, new Object[] { @"C:\......." });
oWord.InvokeMember("Visible", System.Reflection.BindingFlags.SetProperty, null, objClassType, parameter);
I dont know if all these calls are actually needed but it opens the Templete that I am targeting. However, I have no clue as to how to get the MergeFields that I need to replace and then replace them. Can you Tell me just how to find the mergeFields in the target MS Word using late binding and how to write over them. That is really all I needed. I don't think that should be more then about 3-4 lines of code to find MergeFields and Write over them, but I cannot figure it out for the life of me. I meant to state that in the above statement that I did not need the whole thing translated, when I asked for a conversion. I just meant to ask about that one part. Sorry if I made you mad by asking that, it was not my intent.
Thanks.
PS The first code I put in did work fine if you had Word 2007. What you suggested MAY be a better way to do it, but the first way did work.
-
Friday, July 30, 2010 4:39 AM
The following code is my "Reflection" translation of your original code example, up through the first "if" statement that processes a mergefield named "UID". Is this what you're looking for? At the very least, hopefully this gives you an idea of what your code would need to do.
Type applicationType = Type.GetTypeFromProgID("Word.Application"); object applicationObject = Activator.CreateInstance(applicationType); object documentsObject = applicationType.InvokeMember("Documents", System.Reflection.BindingFlags.GetProperty, null, applicationObject, null); applicationType.InvokeMember("Visible", System.Reflection.BindingFlags.SetProperty, null, applicationObject, new object[] { true }); Type documentsType = documentsObject.GetType(); object documentObject = documentsType.InvokeMember("Open", System.Reflection.BindingFlags.InvokeMethod, null, documentsObject, new Object[] { @"C:\Users\mcleans\Documents\Test.docx" }); Type documentType = documentObject.GetType(); object fieldsObject = documentType.InvokeMember("Fields", System.Reflection.BindingFlags.GetProperty, null, documentObject, null); // For some reason, accessing the "Item" property of Fields below does not work if you get the Type by using // fieldsObject.GetType(). Using Assembly.GetType appears to work. Type fieldsType = documentType.Assembly.GetType("Microsoft.Office.Interop.Word.Fields"); int numFields = (int)fieldsType.InvokeMember("Count", System.Reflection.BindingFlags.GetProperty, null, fieldsObject, null); if (numFields > 0) { for (int i = 1; i <= numFields; i++) { System.Reflection.PropertyInfo itemProperty = fieldsType.GetProperty("Item"); object fieldObject = itemProperty.GetValue(fieldsObject, new object[] { i }); Type fieldType = fieldObject.GetType(); object fieldRangeObject = fieldType.InvokeMember("Code", System.Reflection.BindingFlags.GetProperty, null, fieldObject, null); Type fieldRangeType = fieldRangeObject.GetType(); string fieldText = (string)fieldRangeType.InvokeMember("Text", System.Reflection.BindingFlags.GetProperty, null, fieldRangeObject, null); if (fieldText.StartsWith(" MERGEFIELD")) { int endMerge = fieldText.IndexOf("\\"); int fieldNameLength = fieldText.Length - endMerge; string fieldName = fieldText.Substring(11, endMerge - 11); fieldName = fieldName.Trim(); if (fieldName == "UID") { fieldType.InvokeMember("Select", System.Reflection.BindingFlags.InvokeMethod, null, fieldObject, null); object resultObject = fieldType.InvokeMember("Result", System.Reflection.BindingFlags.GetProperty, null, fieldObject, null); Type resultType = resultObject.GetType(); object fontObject = resultType.InvokeMember("Font", System.Reflection.BindingFlags.GetProperty, null, resultObject, null); Type fontType = fontObject.GetType(); object sizeObject = fontType.InvokeMember("Size", System.Reflection.BindingFlags.SetProperty, null, fontObject, new object[] { (float)20.0 }); // and so on... } } } }
Regarding Cindy's comment about the oWordDoc = new Word.Document() line of code in your first example, this code will not give you an actual Document object that represents a document open in Word. In the Word object model, only the Application object can be instantiated directly (i.e. using "new"). Your code worked because a few lines later you set oWordDoc to the return value of oWord.Documents.Add, which is one of the correct ways to get a Document object. If you were to continue to use direct references to PIA types rather than Reflection, you should remove the new Word.Document() code.
This posting is provided "AS IS" with no warranties, and confers no rights.- Marked As Answer by Stizz001 Wednesday, September 15, 2010 6:47 PM
-
Friday, July 30, 2010 9:20 AMModerator
Hi Stizz
You didn't make me angry. But having that much code thrown at you is a bit... daunting.
The code in your recent message could probably be compressed to:
object oDocs = oWord.InvokeMember("Documents", System.Reflection.BindingFlags.GetProperty, null, objClassType, null);
object doc = Docs.InvokeMember("Add", System.Reflection.BindingFlags.InvokeMethod,null, Docs, new object[] { @"C:\......." });Please note that you should declare these objects as type object, not Object (you're not consistent, but as we know, c# does differentiate according to case).
Please also note I've used the ADD, not the OPEN method. If this is a template (*.dot) then you should not open and change it. You will not be able to save the result as a proper *.doc file, it will still be a *.dot file "under the covers". Using Add will create a *.doc that's a copy of the template (no danger of over-writing the template!).
I take it you have no choice but to use this template in this manner? Mail merge fields weren't designed to be used like this, which is why everything seems so complicated. I understand why people use this approach, but it makes me shudder <sigh>
anyway, here's my take on looping the fields, testing for type and changing the content. It's not a direct conversion of the code you got - I did it the way I consider to be optimal.
One thing you need to watch out for is that it's likely the merge fields will revert to the original default if they're updated at any time (which will usually happen on print). You can test this by doing the following in a document: Ctrl+A, F9. If it does revert, you'll need to use the Unlink method on the Fields object to turn the to plain text, or set its Locked property to True (you'll probably need to assign the value -1).
object flds = doc.GetType().InvokeMember("Fields", BindingFlags.GetProperty, null, doc, null); object oNrFlds = flds.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, flds, null); int fld1 = 1; int nrFlds = (int)oNrFlds; for (int fldCounter = fld1; fldCounter <= nrFlds; fldCounter++) { object fld = flds.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, flds, new object[] {fldCounter}); object oFldType = fld.GetType().InvokeMember("Type", BindingFlags.GetProperty, null, fld, null); //Debug.Print(fldType.ToString()); int fldType = (int)oFldType; if (fldType == 59) //it's a merge field { object oFldCodeRange = fld.GetType().InvokeMember("Code", BindingFlags.GetProperty, null, fld, null); object oFldCode = oFldCodeRange.GetType().InvokeMember("Text", BindingFlags.GetProperty, null, oFldCodeRange, null); string fldCode = (string)oFldCode; string fldName = fldCode.Substring(fldCode.Substring(fldCode.IndexOf(" Mergefield ") + 1, 12).Length); Debug.Print(fldName); object fldRange = fld.GetType().InvokeMember("Result", BindingFlags.GetProperty, null, fld, null); fldRange.GetType().InvokeMember("Text", BindingFlags.SetProperty, null, fldRange, new object[] {"new result"}); object oFont = fldRange.GetType().InvokeMember("Font", BindingFlags.GetProperty, null, fldRange, null); oFont.GetType().InvokeMember("Name", BindingFlags.SetProperty, null, oFont, new object[] { "Arial" }); } }
Cindy Meister, VSTO/Word MVP- Edited by Cindy Meister MVPMVP, Moderator Friday, July 30, 2010 9:23 AM remove html from code
- Marked As Answer by Cindy Meister MVPMVP, Moderator Wednesday, September 15, 2010 6:00 AM
-
Wednesday, September 15, 2010 6:51 PM
Thank you guys very much. I will now go try to implement what you have given me. I really appreciate your help.
When I did my searches online, the main method that came up for inserting into the proper space of a template was the MergeField method. Cindy says that these MergeFields were not meant to be used like this, so what method would either of you suggest? I looked at the article link above and there is quite a few for interfacing with Word/Excel. Which would be best for opening Word/Excel and inserting data into certain predetermined areas?
Thanks again.
Stizz
-
Thursday, September 16, 2010 6:36 AMModerator
Hi Stizz
<<When I did my searches online, the main method that came up for inserting into the proper space of a template was the MergeField method. Cindy says that these MergeFields were not meant to be used like this, so what method would either of you suggest? I looked at the article link above and there is quite a few for interfacing with Word/Excel. Which would be best for opening Word/Excel and inserting data into certain predetermined areas?>>
If you're targeting all the way back to Word97, then the "traditional" thing to use would be a Bookmark object. You should find lots of discussions and sample code (mostly VBA, of course) if you "google". The main drawback to this approach is if you have to allow users to create templates. The mail merge approach lets the user link to a database and insert fields without their needing to understand much about what they're doing. Without a customized interface, inserting bookmarks requires more effort on the part of the user to "do it right".
Another possibility would be to insert form fields and write the data to those. You'd use this approach if you wanted to allow the user to edit the data input, but not the actual text.
If you didn't need to support all the way back to Word 97, then I'd say you should generate these files using the OpenXML file format. But the CompatibilityPack support does not extend back to Word 97, only to Word 2000. The main advantage of this would be that your code would be independent of a Word installation (no need for late binding!) and would probably run much faster.
Cindy Meister, VSTO/Word MVP- Marked As Answer by Stizz001 Thursday, September 16, 2010 3:22 PM
-
Thursday, September 16, 2010 3:23 PM
Thanks, I will look into OpenXML as I may not need to go all the way back to 97. I really appreciate your help.
Stizz

