C# MergeField in footer not Working - c#

I am new to C#. I am attempting to simply use Microsoft.Office.Interop.Word
All is working except that the Merge Fields in the footer are not updating. I continue to get the mergecode text only («pname»).
Here is the important part of my code
private void getDoc()
{
String cdir = Directory.GetCurrentDirectory();
btnGetPoa.Visible = false;
var application = new Word.Application();
Object dir = #"../../templates/";
Directory.SetCurrentDirectory(dir.ToString());
var doc = new Word.Document();
var dirf = Directory.GetCurrentDirectory() + "\\poas.docx";
doc = application.Documents.Add(Template: dirf);
foreach (Word.Field fld in doc.Fields)
{
if(fld.Code.Text.Contains("pname"))
{
fld.Select();
application.Selection.TypeText(txtpName.Text.ToString());
}
}
object what = Word.WdGoToItem.wdGoToPage;
object which = Word.WdGoToDirection.wdGoToFirst;
object count = 3;
object missing = Missing.Value;
application.Selection.GoTo(ref what, ref which, ref count, ref missing);
application.Visible = true;
}
Any ideas why the Fields in the Footer are not merging?
Thank you.

The Document object addresses only the main document "story". So querying fields in that object doesn't "see" any fields in headers, footers, Shapes, etc. You need to specifically address these other objects. For a general method on how to address all StoryRange objects, look at the VBA Language Reference example for StoryRange and related. You'll also find discussions and code on this site.
For your specific request, I'm assuming that the document has only the one section and the standard footer - no first page footer or even page footer. (A Word document can have multiple sections and different types of headers and footers, but a "plain vanilla" document has only one of each.)
Word.Range rngFooter = doc.Sections[1].Footers[Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
foreach (Word.Field fld in rngFooter.Fields)
{
}

Related

Threading a C# Word Interop method

I have a C#/WPF application that allows the user to export information into a Word document.
At the moment it works - and creates the document as expected - however the UI locks and the moment I try to thread this method I get varying errors.
The document creation takes in a list of custom items, then builds sections in the Word document based on each item. It creates a table for each image, and in those tables I insert an image placeholder. Once this is done I traverse the document and replace the placeholders with their associated image.
I believe the threading issue is due to the way images are inserted into the document - utilising Clipboard.Clear() and Clipboard.SetDataObject(img).
Is there a cleaner way for me to insert JPG's from disk into the document, or is there a nice way to thread such a method? Here is the offending method:
private static void InsertImagesTables(string document, List<Record> allRecords)
{
Document oDoc = oWord.Documents.Open(document);
Object oMissing = Missing.Value;
object NormalStyle = "Normal";
oWord.Visible = false;
foreach (Record record in allRecords)
{
foreach (RecordImage rImage in record.Images)
{
//insert over placeholder
var range = oDoc.Content;
if (range.Find.Execute("[[" + record.Title + rImage.ImagePath + "]]"))
{
try
{
//insert the image
var prevRange = range.Previous(WdUnits.wdCharacter);
Table imageTable;
imageTable = oDoc.Tables.Add(range, 1, 1, ref oMissing, ref oMissing);
imageTable.Borders.InsideLineStyle = WdLineStyle.wdLineStyleNone;
imageTable.Borders.OutsideLineStyle = WdLineStyle.wdLineStyleNone;
Image img = Image.FromFile(rImage.ImagePath + ".jpg");
Clipboard.Clear();
Clipboard.SetDataObject(img);
imageTable.Cell(1, 1).Range.Paste();
imageTable.Cell(1, 1).Range.set_Style(ref NormalStyle);
imageTable.Cell(1, 1).Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;
InlineShape inlineShape = imageTable.Cell(1, 1).Range.InlineShapes[1];
imageTable.Rows.Alignment = WdRowAlignment.wdAlignRowCenter;
string caption = rImage.Caption;
inlineShape.Range.InsertCaption(Label: "Figure", Title: " - " + caption, Position: WdCaptionPosition.wdCaptionPositionBelow);
range.Expand(WdUnits.wdParagraph);
}
catch // no image for record - do nothing
{ }
}
}
}
oDoc.Close(true);
}
I've tried BackgroundWorkers, Dispatchers, async Tasks and Threads (with and without ApartmentState.STA) with varying outcomes. Most just raise an error, but a few run and complete, without placing every image in the document - such as the STA approach.
Any help here is much appreciated,
Mike
Fixed. Thanks vernou for taking the time. I solved it by removing the Clipboard interaction and instead, properly utilising Word interop with a backgroundworker:
Range cellRange = imageTable.Cell(1, 1).Range;
cellRange.InlineShapes.AddPicture(rImage.ImagePath + ".jpg", ref oMissing, ref oMissing, ref oMissing);
instead of:
Clipboard.Clear();
Clipboard.SetDataObject(img);
imageTable.Cell(1, 1).Range.Paste();

Word Interop Bookmarks Dont Exist

I am currently creating a c# program which uses a template.dotx containing pre-defined building blocks (with bookmarks) to insert pages of content into a new document, all working as follows
Word._Application oWord;
Word._Document oDoc;
object oMissing = System.Reflection.Missing.Value;
object bAddress = "bAddress";
oWord = new Word.Application();
object oTemplate = _template;
oDoc = oWord.Documents.Add(ref oTemplate, ref oMissing,
ref oMissing, ref oMissing);
Word.Template objTmpl = (Word.Template)oDoc.get_AttachedTemplate();
Word.BuildingBlock objBB = objTmpl.BuildingBlockEntries.Item("PageBB");
Range where = oDoc.Range(oDoc.Content.End - 1, oDoc.Content.End - 1);
var orng = objBB.Insert(where, true);
orng.Bookmarks[bAddress].Range.Text = "Address";
I would like to do this in reverse, to open created file at a later date, and read through each bookmark to get the value.
The problem is when I open the resulting DocXCreatedFromTemplate.docx, the bookmarks have disappeared. Programmatically, I receive the bookmark not in collection error, but also by allowing word to open and checking manually the bookmarks are replaced with text but the bookmark reference has gone.
Is there any way to get round this?
I have worked this out, the following text replaces and deletes the bookmark
orng.Bookmarks[bAddress].Range.Text = "Address";
The solution was to re-add the bookmark afterwards using a method as follows
private void UpdateBookmarkWithoutDeleting(object bookmark, string text, _Document document)
{
var bookmarkRange = document.Bookmarks[bookmark].Range;
bookmarkRange.Text = text;
document.Bookmarks.Add(bookmark.ToString(), bookmarkRange);
}
I found the information at the following link (in VB):
https://wordmvp.com/FAQs/MacrosVBA/InsertingTextAtBookmark.htm

In c# using microsoft.office.tools.word how do we save a single page as rtf file

In c# using microsoft.office.tools.word how do we save a single page of a document as rtf file.
Lets say only Page 5 has to be saved as a rtf.
I cannot see any thing like Document.Pages[5] to access a certain page.
This saves page 2 as "page2.pdf"
Document d = wordApp.ActiveDocument;
object what = Microsoft.Office.Interop.Word.WdGoToItem.wdGoToPage;
object which = Microsoft.Office.Interop.Word.WdGoToDirection.wdGoToFirst;
object count = 2;
Range r =wordApp.Selection.GoTo(ref what, ref which, ref count);
count = (int)count+1;
Range r2 = wordApp.Selection.GoTo(ref what, ref which, ref count);
r.End = r2.End;
r.Select();
r.ExportAsFixedFormat(#"d:\temp\page2.pdf", WdExportFormat.wdExportFormatPDF);
It should give ideas on how to select another page, and saving it as RTF...
Pages are another story:
Pages pages = wordApp.ActiveDocument.ActiveWindow.ActivePane.Pages;
MessageBox.Show("This document has "+pages.Count+" pages");
But my knowledge about this is limited enough to stop after these 2 lines ... 😁
EDIT: I could not find a solution to write the selected text to a RTF but, you can always copy it to a new document, and then SaveAs....
With the selection:
r.Copy();
Document dtmp = wordApp.Documents.Add();
dtmp.Activate();
Selection sel = wordApp.ActiveWindow.ActivePane.Selection;
sel.Paste();
dtmp.SaveAs2(#"d:\temp\page2.rtf", WdSaveFormat.wdFormatRTF);
dtmp.Close();

Convert a Word (DOCX) file to a PDF in C# on cloud environment

I have generated a word file using Open Xml and I need to send it as attachment in a email with pdf format but I cannot save any physical pdf or word file on disk because I develop my application in cloud environment(CRM online).
I found only way is "Aspose Word to .Net".
http://www.aspose.com/docs/display/wordsnet/How+to++Convert+a+Document+to+a+Byte+Array But it is too expensive.
Then I found a solution is to convert word to html, then convert html to pdf. But there is a picture in my word. And I cannot resolve the issue.
The most accurate conversion from DOCX to PDF is going to be through Word. Your best option for that is setting up a server with OWAS (Office Web Apps Server) and doing your conversion through that.
You'll need to set up a WOPI endpoint on your application server and call:
/wv/WordViewer/request.pdf?WOPISrc={WopiUrl}&type=downloadpdf
OR
/wv/WordViewer/request.pdf?WOPISrc={WopiUrl}&type=printpdf
Alternatively you could try and do it using OneDrive and Word Online, but you'll need to work out the parameters Word Online uses as well as whether that's permitted within the Ts & Cs.
You can try Gnostice XtremeDocumentStudio .NET.
Converting From DOCX To PDF Using XtremeDocumentStudio .NET
http://www.gnostice.com/goto.asp?id=24900&t=convert_docx_to_pdf_using_xdoc.net
In the published article, conversion has been demonstrated to save to a physical file. You can use documentConverter.ConvertToStream method to convert a document to a Stream as shown below in the code snippet.
DocumentConverter documentConverter = new DocumentConverter();
// input can be a FilePath, Stream, list of FilePaths or list of Streams
Object input = "InputDocument.docx";
string outputFileFormat = "pdf";
ConversionMode conversionMode = ConversionMode.ConvertToSeperateFiles;
List<Stream> outputStreams = documentConverter.ConvertToStream(input, outputFileFormat, conversionMode);
Disclaimer: I work for Gnostice.
If you wanna convert bytes array, then to use Metamorphosis:
string docxPath = #"example.docx";
string pdfPath = Path.ChangeExtension(docxPath, ".pdf");
byte[] docx = File.ReadAllBytes(docxPath);
// Convert DOCX to PDF in memory
byte[] pdf = p.DocxToPdfConvertByte(docx);
if (pdf != null)
{
// Save the PDF document to a file for a viewing purpose.
File.WriteAllBytes(pdfPath, pdf);
System.Diagnostics.Process.Start(pdfPath);
}
else
{
System.Console.WriteLine("Conversion failed!");
Console.ReadLine();
}
I have recently used SautinSoft 'Document .Net' library to convert docx to pdf in my React(frontend), .NET core(micro services- backend) application. It only take 15 seconds to generate a pdf having 23 pages. This 15 seconds includes getting data from database, then merging data with docx template and then converting it to pdf. The code has deployed to azure Linux box and works fine.
https://sautinsoft.com/products/document/
Sample code
public string GeneratePDF(PDFDocumentModel document)
{
byte[] output = null;
using (var outputStream = new MemoryStream())
{
// Create single pdf.
DocumentCore singlePDF = new DocumentCore();
var documentCores = new List<DocumentCore>();
foreach (var section in document.Sections)
{
documentCores.Add(GenerateDocument(section));
}
foreach (var dc in documentCores)
{
// Create import session.
ImportSession session = new ImportSession(dc, singlePDF, StyleImportingMode.KeepSourceFormatting);
// Loop through all sections in the source document.
foreach (Section sourceSection in dc.Sections)
{
// Because we are copying a section from one document to another,
// it is required to import the Section into the destination document.
// This adjusts any document-specific references to styles, bookmarks, etc.
// Importing a element creates a copy of the original element, but the copy
// is ready to be inserted into the destination document.
Section importedSection = singlePDF.Import<Section>(sourceSection, true, session);
// First section start from new page.
if (dc.Sections.IndexOf(sourceSection) == 0)
importedSection.PageSetup.SectionStart = SectionStart.NewPage;
// Now the new section can be appended to the destination document.
singlePDF.Sections.Add(importedSection);
//Paging
HeaderFooter footer = new HeaderFooter(singlePDF, HeaderFooterType.FooterDefault);
// Create a new paragraph to insert a page numbering.
// So that, our page numbering looks as: Page N of M.
Paragraph par = new Paragraph(singlePDF);
par.ParagraphFormat.Alignment = HorizontalAlignment.Center;
CharacterFormat cf = new CharacterFormat() { FontName = "Consolas", Size = 11.0 };
par.Content.Start.Insert("Page ", cf.Clone());
// Page numbering is a Field.
Field fPage = new Field(singlePDF, FieldType.Page);
fPage.CharacterFormat = cf.Clone();
par.Content.End.Insert(fPage.Content);
par.Content.End.Insert(" of ", cf.Clone());
Field fPages = new Field(singlePDF, FieldType.NumPages);
fPages.CharacterFormat = cf.Clone();
par.Content.End.Insert(fPages.Content);
footer.Blocks.Add(par);
importedSection.HeadersFooters.Add(footer);
}
}
var pdfOptions = new PdfSaveOptions();
pdfOptions.Compression = false;
pdfOptions.EmbedAllFonts = false;
pdfOptions.EmbeddedImagesFormat = PdfSaveOptions.EmbImagesFormat.Png;
pdfOptions.EmbeddedJpegQuality = 100;
//dont allow editing after population, also ensures content can be printed.
pdfOptions.PreserveFormFields = false;
pdfOptions.PreserveContentControls = false;
if (!string.IsNullOrEmpty(document.PdfProperties.Title))
{
singlePDF.Document.Properties.BuiltIn[BuiltInDocumentProperty.Title] = document.PdfProperties.Title;
}
if (!string.IsNullOrEmpty(document.PdfProperties.Author))
{
singlePDF.Document.Properties.BuiltIn[BuiltInDocumentProperty.Author] = document.PdfProperties.Author;
}
if (!string.IsNullOrEmpty(document.PdfProperties.Subject))
{
singlePDF.Document.Properties.BuiltIn[BuiltInDocumentProperty.Subject] = document.PdfProperties.Subject;
}
singlePDF.Save(outputStream, pdfOptions);
output = outputStream.ToArray();
}
return Convert.ToBase64String(output);
}

C# Save duplicate of Active document, but include track changes

I'm working on Word 2010 plugin and I want to copy active document with track changes (http://office.microsoft.com/en-001/word-help/turn-track-changes-on-or-off-HA010370561.aspx) to XML format and later to send it somewhere else.
This is my code:
Microsoft.Office.Interop.Word.Document documentNew = new Microsoft.Office.Interop.Word.Document();
object missing = Type.Missing;
document.Range(ref missing, ref missing).Copy();
documentNew.Range(ref missing, ref missing).PasteAndFormat(Microsoft.Office.Interop.Word.WdRecoveryType.wdFormatOriginalFormatting);
Object xmlFormat = Microsoft.Office.Interop.Word.WdSaveFormat.wdFormatXML;
documentNew.SaveAs2(file, xmlFormat);
This works, but does not include track changes in duplicate document. Anybody have idea how to also include changes?
How about saving document to the new document in XML first, then opening up the new document and make whatever changes are required? I've tested this approach and it preserves the tracked changed without having to do anything special.
Copy and pasting into a new document is not going to preserve the original tracked changes.
So the code would be:
public static void SaveAsXMLAndDoSomethingElse() {
String fn = #"C:\Users\zbook\Desktop\Track test.docx";
String fn_xml = #"C:\Users\zbook\Desktop\Track test3.xml";
Microsoft.Office.Interop.Word.Application app = new Microsoft.Office.Interop.Word.Application();
Documents docs = app.Documents;
Document doc = docs.Open(fn, ReadOnly:true);
//bool b = doc.TrackFormatting; // for some reason this line bombs
doc.SaveAs2(fn_xml, Microsoft.Office.Interop.Word.WdSaveFormat.wdFormatXML);
doc.Close(false);
Marshal.ReleaseComObject(doc);
// now open up fn_xml ... and do whatever
app.Quit(false);
Marshal.ReleaseComObject(docs);
Marshal.ReleaseComObject(app);
}

Categories