C# Interop.Word.Application/Document ambiguity after Direct Cast - c#

I have seen some other questions on here which has solutions, however even direct cast and garbage collection won't get rid of the ambiguity warning or stop my program trying to use a document that is apparently already in use.
The below code is part of an application to completely rebrand some Word documents (changing headers, templates and logos etc). MergeDocument isn't disposing of the word application or document (a template file that other methods need access too).
The WaitForIt method is simply a timer delay of two seconds, in hope that it was a timing issue in regards to accessing the template file. Most of the other methods don't get a chance to do anything before the try/catch in GrabLogoFromTemplate is hit when it tries to use the same template that MergeDocument has used.
Unfortunately it isn't and I'm now out of ideas as what to do to combat this, direct casting as mentioned in the title didn't rectify what I assume is the issue with the Introp Word Application and Document Close and Quite methods. The same as reported in this question, and hereI tried the solution of garbage collection noted in the latter, but it didn't solve my issue. Both Close() and Quit() are ambiguous, like the issue in the first question, but even disposing (think I have done this correctly) doesn't free the template for the other methods to use.
Has anyone come across this before?
Merge Button:
private void Merge_btn_Click(object sender, EventArgs e)
{
Rebrand rb = new Rebrand();
progressBar1.Increment(+1);
rb.FolderCreation();
Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
progressBar1.Increment(+24);
string[] fileArray = Directory.GetFiles(currentFolder_tb.Text);
for (int i=0; i < fileArray.Length; i++)
{
string tmpTemplate = rb.GetTempTemplateLocationn(template_tb.Text,i);
string newDocumentPath = rb.MergeDocument(fileArray[i], newLocation_tb.Text, tmpTemplate, wordApp);
// ambiguous
foreach(Word.Document d in wordApp.Documents)
{
d.Close(ref missing, ref missing, ref missing);
}
rb.WaitForIt();
rb.GrabLogoFromTemplate(tmpTemplate);
rb.WaitForIt();
if (newDocumentPath != "false")
rb.ReplaceImageInHeader(newDocumentPath);
}
object missing = System.Reflection.Missing.Value;
((Microsoft.Office.Interop.Word.Application)wordApp).Quit(ref missing, ref missing, ref missing);
// the above has ambiguity warning
GC.Collect();// doesn't solve
if (progressBar1.Value == 99)
progressBar1.Increment(+1);
MessageBox.Show("File(s) ReBranded!");
progressBar1.Value = 0;
}
Merge Document:
public string MergeDocument(string oldDoc, string newDoc, string tmplt, Microsoft.Office.Interop.Word.Application wordApp)
{
try
{
object missing = System.Reflection.Missing.Value;
//Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
Microsoft.Office.Interop.Word.Document aDoc = null;
object readOnly = false;
object isVisible = false;
wordApp.Visible = false;
object oldfile = oldDoc;
// keep the old document's filename
object newfile = newDoc + #"\" + Path.GetFileName(oldDoc);
object template = tmplt;
// add new template
aDoc = wordApp.Documents.Add(ref template, ref missing, ref missing, ref missing);
// open existing document you wish to merge
aDoc = wordApp.Documents.Open(ref oldfile, ref missing, ref readOnly, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref isVisible,
ref missing, ref missing, ref missing, ref missing);
aDoc.Activate();
aDoc.set_AttachedTemplate(template);
aDoc.UpdateStyles();
aDoc.SaveAs(ref newfile, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
// close the document, ambiguity warning
((Microsoft.Office.Interop.Word.Document)aDoc).Close(ref missing, ref missing, ref missing);
return newfile.ToString();
}
catch
{
return "false";
}
}

I had a problem batch processing PowerPoint slides. The only solution was to close, quit and then run GC twice!
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

I don't fully understand why this works, but it was the solution for me, essentially destroying objects twice.
Over-the-top, as essentially I'm disposing of the same object multiple times, but it does the trick.
The ambiguity is still present, which is probably why I needed to 'double tap' each object.
Within MergeDocument() underneath close() I added:
if(aDoc != null)
System.Runtime.InteropServices.Marshal.ReleaseComObject(aDoc);
aDoc = null;
Before GrabLogoFromTemplate() I added a loop to destroy all Document types remaining in my Application instance:
foreach(Word.Document d in wordApp.Documents)
{
d.Close(ref missing, ref missing, ref missing);
}
Finally, just before I call the GC I made sure that the Application instance is set to null and release the Introp ComObject:
if(wordApp != null)
System.Runtime.InteropServices.Marshal.ReleaseComObject(wordApp);
wordApp = null;

Related

How to merge two Word Document Objects in C# (not on file system level)?

i want to append one Word Document Object to another in my C# Code.
Is there any way to do this instantly with Word Interop Functions when i have both Documents as an Object?
I tried several approaches with Merge Function but this function expects filenames as strings and no objects. I don't want to save both documents and load them again.
//schematic version of my code:
doc_template = wordapp.Documents.Open(ref filedocA,
ref readOnly, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing,
ref missing);
doc_final = wordapp.Documents.Add(ref missing, ref missing, ref missing, ref missing);
wordapp.Document tempdoc = null;
foreach (customer cust in dt_customers)
{
tempdoc = doc_template;
//Do some search and replace functions in tempdoc
...
//here somehow the complete tempdoc should be appended to doc_final
}
doc_final should contain all the modified tempdoc from each customer
//Update: Heres what i am doing now: Copying the contents within ranges
object start = tempdoc.Content.Start;
object end = tempdoc.Content.End;
tempdoc.Range(ref start, ref end).Copy();
doc_final.Activate();
start = Destination.Content.End;
end = Destination.Content.End;
start = wordapp.ActiveDocument.Content.End - 1;
rng = Destination.Range(ref start, ref missing);
rng.Select();
rng.Paste();
But is there a better option than simple copy the contents of the documents?

Getting a specific page from Word

how to get specific page from word in c#.net console application?
I have tried it ,
But unfortunately I have got error from my application.
Following is my code:
{
object what = WdGoToItem.wdGoToPage;
object which = WdGoToDirection.wdGoToFirst;
object count = 0;
const string fileName = #"C:\..\..\test.doc";
object fileNameAsObject = fileName;
Application wordApplication = new Application();
object readOnly = false;
object missing = System.Reflection.Missing.Value;
wordApplication.Documents.Open(ref fileNameAsObject, ref missing, ref readOnly, 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);
// here on this following line I have got error "This method or property is not available because this command is not available for reading."
Range startRange = wordApplication.Selection.GoTo(ref what, ref which, ref count, ref missing);
object count2 = (int)count + 1;
Range endRange = wordApplication.Selection.GoTo(ref what, ref which, ref count2, ref missing);
endRange.SetRange(startRange.Start, endRange.End);
endRange.Select();
;
}
So please provide me any solution on it..
Thanks In advance..
Are you using Office 2013? We ran into an issue with the same error message when running our interop code against a freshly installed Office 2013. It seems to be due to Office 2013's default "Reading Mode" as mentioned here.
Try turning off the reading mode by setting Application.ActiveWindow.View.ReadingLayout to false (as mentioned in the comments of the article). This call must be performed after you have opened a document. Otherwise, the call would fail with the message: System.Runtime.InteropServices.COMException : This command is not available because no document is open.
If you can I would use Open XML SDK 2.5 for Microsoft Office
This gives you full access to the document and in my opinion is the most likely to work and not have any memory problems.
After first call Word application still blocks document. Therefore document is read only.
Kill WINWORD.EXE process and then change code to:
Document document = wordApplication.Documents.Open(ref fileNameAsObject, ref missing, ref readOnly, 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);
After work close document:
document.Close();
wordApplication.Quit();
After modifications working code:
object what = WdGoToItem.wdGoToPage;
object which = WdGoToDirection.wdGoToFirst;
object count = 0;
const string fileName = #"C:\..\..\test.doc";
object fileNameAsObject = fileName;
Application wordApplication = new Application();
object readOnly = false;
object missing = System.Reflection.Missing.Value;
wordApplication.Documents.Open(ref fileNameAsObject, ref missing, ref readOnly, 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);
// here on this following line I have got error "This method or property is not available because this command is not available for reading."
Range startRange = wordApplication.Selection.GoTo(ref what, ref which, ref count, ref missing);
object count2 = (int)count + 1;
Range endRange = wordApplication.Selection.GoTo(ref what, ref which, ref count2, ref missing);
endRange.SetRange(startRange.Start, endRange.End);
endRange.Select();
wordApplication.Documents.Close();
wordApplication.Quit();
I know its old but I didnt get a proper answer so my solution is here it works fine.
object missing = System.Reflection.Missing.Value;
var document = application.ActiveDocument;
Word.WdStatistic stat = Word.WdStatistic.wdStatisticPages;
int num = aDoc.ComputeStatistics(stat, ref missing); // Get number of pages
for(int i=0; i<num; i++)
{
document.ActiveWindow.Selection // Go to page "i"
.GoTo(WdGoToItem.wdGoToPage, missing, missing, i.ToString());
document.ActiveWindow.Selection // Select whole page
.GoTo(WdGoToItem.wdGoToBookmark, missing, missing, "\\page");
document.ActiveWindow.Selection.Copy(); // Copy to clipboard
// Do whatever you want with the selection
}

Merging multiple docx files to one

I am developing a desktop application in C#. I have coded a function to merge multiple docx files but it does not work as expected. I don't get the content exactly as how it was in the source files.
A few blank lines are added in between. The content extends to the next pages, header and footer information is lost, page margins gets changed, etc..
How can I concatenate docs as it is without and change in it.Any suggestions will be helpful.
This is my code.
public bool CombineDocx(string[] filesToMerge, string destFilepath)
{
Application wordApp = null;
Document wordDoc = null;
object outputFile = destFilepath;
object missing = Type.Missing;
object pageBreak = WdBreakType.wdPageBreak;
try
{
wordApp = new Application { DisplayAlerts = WdAlertLevel.wdAlertsNone, Visible = false };
wordDoc = wordApp.Documents.Add(ref missing, ref missing, ref missing, ref missing);
Selection selection = wordApp.Selection;
foreach (string file in filesToMerge)
{
selection.InsertFile(file, ref missing, ref missing, ref missing, ref missing);
selection.InsertBreak(ref pageBreak);
}
wordDoc.SaveAs( ref outputFile, 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);
return true;
}
catch (Exception ex)
{
Msg.Log(ex);
return false;
}
finally
{
if (wordDoc != null)
{
wordDoc.Close();
}
if (wordApp != null)
{
wordApp.DisplayAlerts = WdAlertLevel.wdAlertsAll;
wordApp.Quit();
Marshal.FinalReleaseComObject(wordApp);
}
}
}
In my opinion it's not so easy. Therefore I'll give you some tips here.
I think you need to implement the following changes to your code.
1.instead of pageBreak you need to add any of section breaks which could be the most appropriate:
object sectionBrak = WdBreakType.wdSectionBreakNextPage;
'other section break types also available
and use this new variable within your loop.
As a result you get all- text, footers and headers of the source document to new one.
2.However, you will still need to read margin parameters and apply them to your new document 'manually' using additional code. Therefore you will need to open source document and check it's margins in this way:
intLM = SourceDocument.Sections(1).PageSetup.LeftMargin;
'...and similar for other margins
and next you need to apply it to new document, to appropriate section:
selection.Sections(1).PageSetup.LeftMargin = intLM;
3.Some other document section could require some other techniques.
You could use the Open XML SDK and the DocumentBuilder tool.
See Merge multiple word documents into one Open Xml

C# Word Document - How to clean formatting?

The dilemma is rather simple. I need to create a small app that will clear all font background colors (leave table cell background colours unchanged), and remove all text with strikethrough in a word document, and then save the document into another folder. Otherwise the document's formatting should remain untouched.
Below is a large-ish example scraped together from random examples available in google showing how to apply specific kinds of formatting to random strings found using Find.Execute(). I have no clue however, on how to only do as described above.
public static string searchDoc(string fileNameRef)
{
Microsoft.Office.Interop.Word._Application word = new Microsoft.Office.Interop.Word.Application(); ;
Microsoft.Office.Interop.Word._Document doc = new Microsoft.Office.Interop.Word.Document();
object missing = System.Type.Missing;
try
{
System.IO.FileInfo ExecutableFileInfo =
new System.IO.FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location);
object fileName =
System.IO.Path.Combine(ExecutableFileInfo.DirectoryName, fileNameRef);
doc = word.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);
doc.Activate();
//object findStr = "hello"; //sonething to find
// THIS is the part where I fail, I can't find of a way to Find.Execute on formatting
// as opposed to mere strings.
//while (word.Selection.Find.Execute(ref findStr)) //found...
//{
// //change font and format of matched words
// word.Selection.Font.Name = "Tahoma"; //change font to Tahoma
// word.Selection.Font.ColorIndex = Microsoft.Office.Interop.Word.WdColorIndex.wdRed; //change color to red
//}
object saveFileName = ExecutableFileInfo.DirectoryName + "\\New\\" + fileNameRef;
doc.SaveAs(ref saveFileName, 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);
}
catch (Exception)
{
}
finally
{
doc.Close(ref missing, ref missing, ref missing);
word.Application.Quit(ref missing, ref missing, ref missing);
}
return fileNameRef;
}
Thanks for any help! And I do mean any, simply getting started on how to spot formatting would help a great deal, I imagine. :)
This is not a C#-specific question; it's a Word Object Model question (I refer you to here and here).
As to your specific question, I suggest you turn on the Macro Recorder in Word, perform the actions, and see the generated VBA code. Then you can apply it in C#.
Try this:
using System;
using Microsoft.Office.Interop.Word;
using System.IO;
using System.Reflection;
namespace WordFormattingFindReplace {
class Program {
static void Main(string[] args) {
}
public static string searchDoc(string fileName) {
_Application word = new Application(); ;
_Document doc;
string folderName = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
string filePath = Path.Combine(folderName,fileName);
doc = word.Documents.Open(filePath);
var find=doc.Range().Find;
find.Text="Hello";
find.Format=true;
find.Replacement.Font.Name="Tahoma";
find.Replacement.Font.ColorIndex=WdColorIndex.wdRed;
find.Execute(Replace:WdReplace.wdReplaceAll);
doc.SaveAs2(Path.Combine(folderName,"New",fileName));
doc.Close();
//We need to cast this to _Application to resolve which Quit method is being called
((_Application)word.Application).Quit();
return fileName;
}
}
}
Some notes:
Use using statements for clarity. Instead of Microsoft.Office.Interop.Word._Application word, add using Microsoft.Office.Interop.Word at the top of your file, and you can then just write _Application word
If all you need is the folder name, use the static Path.GetDirectoryName method and save as a string variable, instead of creating a FileInfo object
As of .NET 4, you can skip optional arguments when calling Documents.Open, Document.SaveAs and Document.Close. This also means you don't need an object missing.
There's nothing here the user really needs to see, so calling Document.Activate is unnecessary
It's probably better to reuse the Word.Application instance, instead of recreating it for each call.

inserting selected page from one word document in another word document with c#

I have a requirement to move selected pages from word DocumentA into another word DocumentB.
So in the end DocumentB should have its own contents plus selected pages from DocumentA inserted at selected pages in DocumentB. The page number in DocumentB I will set thru properties.
This is the code I am using to just append contents of DocumentA to DocumentB.
object missing = System.Reflection.Missing.Value;
Word._Application wordApp = new Word.Application();
Word._Document aDoc = new Word.Document();
try
{
wordApp.Visible = false;
object readOnly = false;
object isVisible = false;
aDoc = wordApp.Documents.Open(ref fPath1, ref missing, ref readOnly, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref isVisible, ref missing, ref missing, ref missing, ref missing);
Word.Selection selection = wordApp.Selection;
selection.InsertFile(fPath2, ref missing, ref missing, ref missing, ref missing);
aDoc.Save();
wordApp.Quit(ref missing, ref missing, ref missing);
}
catch(Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
wordApp = null;
aDoc = null;
}
However, I keep getting this exception 'object reference not set to instance of object' at the line 'selection.InsertFile...'
What is going wrong here?
And how do I insert contents of page 2 from DocumentA into page 3 of DocumentB?
Thanks for your time.
Are you selecting text in Word and then running this? I imagine you'd get that exception if no text was selected in Word.
I have been doing abit of Visio interop and selection is an array, so it could be you have not added anything to the wordApp.Selection.

Categories

Resources