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
Related
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?
I need your help with inserting text in a particular area in a word document.
I have a following code that inserts text from a .txt file to the Word document, but I need it to be placed in an exact area not just anywhere.
My Code:
string[] readText = File.ReadAllLines(#"p:\CARTAP1.txt");
foreach (string s in readText)
{
Console.WriteLine(s);
}
Application application = new Application();
Document document = application.Documents.Open(#"P:\PreciboCancelado.doc");
application.Visible = true;
application.Selection.TypeText(readText[2]);
I found a way to do it using bookmarks just as Manuel stated:
string[] readText = File.ReadAllLines(#"p:\CARTAP1.txt");
// declare objects and variables
object fileName = #"P:\PreciboCancelado.doc";
object readOnly = false;
object isVisible = true;
object missing = System.Reflection.Missing.Value;
// create instance of Word
Microsoft.Office.Interop.Word.ApplicationClass oWordApp = new Microsoft.Office.Interop.Word.ApplicationClass();
// Create instance of Word document
Microsoft.Office.Interop.Word.Document oWordDoc = new Document();
// Open word document.
oWordDoc = oWordApp.Documents.Open(ref fileName, ref missing, ref readOnly, ref readOnly,
ref missing, ref missing, ref readOnly, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing);
oWordDoc.Activate();
// Debug to see that I can write to the document.
oWordApp.Selection.TypeText("This is the text that was written from the C# program! ");
oWordApp.Selection.TypeParagraph();
// Example of writing to bookmarks each bookmark will have exists around it to avoid null.
if (oWordDoc.Bookmarks.Exists("Fecha"))
{
// set value for bookmarks
object oBookMark = "Fecha";
oWordDoc.Bookmarks.get_Item(ref oBookMark).Range.Text = readText[2] ;
oBookMark = "Nombre";
oWordDoc.Bookmarks.get_Item(ref oBookMark).Range.Text = readText[3];
}
// Save the document.
oWordApp.Documents.Save(ref missing, ref missing);
// Close the application.
oWordApp.Application.Quit(ref missing, ref missing, ref missing);
You can try it with a bookmark in Word. Perhaps this link helps you http://gregmaxey.mvps.org/word_tip_pages/insert_text_at_or_in_bookmark.html
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;
My tool will process more than 1000 docs. We had set Readonly at document level, which leads to severe performance issue.
_appObject = new Microsoft.Office.Interop.Word.Application();
Microsoft.Office.Interop.Word.Document _DocObj;
string file = #”c:\Users\Public\Public Documents\Word12.docx”;
_DocObj = _appObject.Documents.Open(ref file, 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);
//protect
appObject.ActiveDocument.Protect(Microsoft.Office.Interop.Word.WdProtectionType
.wdAllowOnly Reading, ref noReset, ref password, ref useIRM, ref enforceStyleLock);
But I want to make the Paragraph or range to readonly
foreach (Microsoft.Office.Interop.Word.Paragraph aPar in
_appObject.ActiveDocument.Paragraphs)
{
Microsoft.Office.Interop.Word.Range parRng = aPar.Range;
string sText = parRng.Text;
// I want to make readonly here
}
Then the doc will get saved.
_DocObj.SaveAs(FileName: TargetDir, FileFormat: WdSaveFormat.wdFormatDocumentDefault);
object saveChanges = WdSaveOptions.wdSaveChanges;
object originalFormat = WdOriginalFormat.wdOriginalDocumentFormat;
object routeDocument = true;
islockStatus = true;
var doc_close = (Microsoft.Office.Interop.Word._Document)_DocObj;
doc_close.Close(ref saveChanges, ref originalFormat, ref routeDocument);
Hence the requirement is like that to make a portion of word document (especially HEADING or paragraph or alteast range)
If you have a Range object, then you can use Editors member to access the list of users that are allowed to edit that range.
In your case, you would want to enable "everyone" to edit the entire document, then remove permission to edit specific paragraphs.
In VBA, this would look something like this (I'm sure you can translate this to C#):
' Allow access to the entire doc
ActiveDocument.Content.Editors.Add wdEditorEveryone
' Remove access to paragraph 1
ActiveDocument.Content.Paragraphs(1).Editors(wdEditorEveryone).Delete
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.