Interop Word - Insert Mergefield to the End of the Range [duplicate] - c#

I have created a 1x3 table as my header in word. This is how I want it to look like.
LeftText MiddleText PageNumber:
I want the PageNumber cell to look like this -
Page: X of Y
I have managed to do cell (1,1) and (1,2). I found this to help me with cell (1,3) but it is not working as I like. I know how to get the total count of the document. I'm not sure how to implement it properly.
Range rRange = restheaderTable.Cell(1, 3).Range;
rRange.End = rRange.End - 1;
oDoc.Fields.Add(rRange, Type: WdFieldType.wdFieldPage, Text: "Page Number: ");
I can't even get the Text "Page Number: " to display in the cell. All it has is a number right now.

The field enumeration you're looking for is WordWdFieldType.wdFieldNumPages.
The next hurdle is how to construct field + text + field as Word doesn't behave "logically" when things are added in this order. The target point remains before the field that's inserted. So it's either necessary to work backwards, or to move the target range after each bit of content.
Here's some code I have the demonstrates the latter approach. Inserting text and inserting fields are in two separate procedures that take the target Range and the text (whether literal or the field text) as parameters. This way the field code can be built up logically (Page x of n). The target Range is returned from both procedures, already collapsed to its end-point, ready for appending further content.
Note that I prefer to construct a field using the field's text (including any field switches) rather than specifying a field type (the WdFieldType enumeration). This provides greater flexibility. I also highly recommend setting the PreserveFormatting parameter to false as the true setting can result in very odd formatting when fields are updated. It should only be used in very specific instances (usually involving linked tables).
private void btnInsertPageNr_Click(object sender, EventArgs e)
{
getWordInstance();
Word.Document doc = null;
if (wdApp.Documents.Count > 0)
{
doc = wdApp.ActiveDocument;
Word.Range rngHeader = doc.Sections[1].Headers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
if (rngHeader.Tables.Count > 0)
{
Word.Table tbl = rngHeader.Tables[1];
Word.Range rngPageNr = tbl.Range.Cells[tbl.Range.Cells.Count].Range;
//Collapse the range so that it's within the cell and
//doesn't include the end-of-cell markers
object oCollapseStart = Word.WdCollapseDirection.wdCollapseStart;
rngPageNr.Collapse(ref oCollapseStart);
rngPageNr = InsertNewText(rngPageNr, "Page ");
rngPageNr = InsertAField(rngPageNr, "Page");
rngPageNr = InsertNewText(rngPageNr, " of ");
rngPageNr = InsertAField(rngPageNr, "NumPages");
}
}
}
private Word.Range InsertNewText(Word.Range rng, string newText)
{
object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
rng.Text = newText;
rng.Collapse(ref oCollapseEnd);
return rng;
}
private Word.Range InsertAField(Word.Range rng,
string fieldText)
{
object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
object unitCharacter = Word.WdUnits.wdCharacter;
object oOne = 1;
Word.Field fld = rng.Document.Fields.Add(rng, missing, fieldText, false);
Word.Range rngField = fld.Result;
rngField.Collapse(ref oCollapseEnd);
rngField.MoveStart(ref unitCharacter, ref oOne);
return rngField;
}

Related

c# VSTO Loop through each row and set format based on another cell value

I have VSTO application that outputs rows into excel. I am trying to apply some formatting based on values in a cell. My requirement is if the column 'V' contains a value "No" in the cell, the entire row that has values in it needs to be of a background colour "light grey". Note: Column V is a drop down list of either yes or no in excel. So its not a normal text based cell. Any ideas on how to do this?
var last = uiWorksheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell, Type.Missing);
var lastUsedRow = last.Row < 6 ? 6 : last.Row;
var lastUsedColumn = last.Column;
var xlrange = uiWorksheet.get_Range("V:V");
if (xlrange.Value2.ToString() == "No")
{
???.EntireRow.Interior.Color = Microsoft.Office.Interop.Excel.XlRgbColor.rgbLightGray;
}
In order to look for a value in the column, you need to use Find and FindNext functions.
After it, you need to iterate over the results and change the color.
Please note following from MSDN:
The FindNext method's search wraps back to the beginning of the search
range after it has reached the end of the range. Your code must ensure
that the search does not wrap around in an infinite loop.
var xlrange = uiWorksheet.get_Range("V:V");
var searchFor = "No";
Range firstResult = null;
var currentResult = xlrange.Find(What: searchFor,
LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole,
SearchOrder: XlSearchOrder.xlByRows
);
while(currentResult != null)
{
if (firstResult == null)
firstResult = currentResult;
else if (currentResult.get_Address(XlReferenceStyle.xlA1)
== firstResult.get_Address(XlReferenceStyle.xlA1))
{
break;
}
currentResult.EntireRow.Interior.Color = XlRgbColor.rgbLightGray;
currentResult = xlrange.FindNext(currentResult);
}

Add table in current position with a word add-in

I want to add a table with my Word Add-in with data from my database.
I've done that successfully but now I have a problem with the position of the table.
I want to place it exactly where my current position in the Word document is.
But so it's always added at the beginning.
Does anyone know how to adjust the range that my start value is always my current position?
This is a part of my code:
private void createTable_Click(object sender, EventArgs e) {
object start = 0, end = 0;
Word.Document document = Globals.ThisAddIn.Application.ActiveDocument;
Word.Range rng = document.Range(ref start, ref end);
// Insert a title for the table and paragraph marks.
rng.InsertBefore("List");
rng.Font.Name = "Verdana";
rng.Font.Size = 16;
rng.InsertParagraphAfter();
rng.InsertParagraphAfter();
rng.SetRange(rng.End, rng.End);
// Add the table.
rng.Tables.Add(document.Paragraphs[2].Range, 1, 7, ref missing, ref missing);
// Format the table and apply a style.
Word.Table tbl = document.Tables[1]; tbl.Range.Font.Size = 8;
tbl.Borders[WdBorderType.wdBorderLeft].LineStyle =
WdLineStyle.wdLineStyleSingle;
tbl.Borders[WdBorderType.wdBorderRight].LineStyle =
WdLineStyle.wdLineStyleSingle;
tbl.Borders[WdBorderType.wdBorderTop].LineStyle =
WdLineStyle.wdLineStyleSingle;
tbl.Borders[WdBorderType.wdBorderBottom].LineStyle =
WdLineStyle.wdLineStyleSingle;
tbl.Borders[WdBorderType.wdBorderHorizontal].LineStyle =
WdLineStyle.wdLineStyleSingle;
tbl.Borders[WdBorderType.wdBorderVertical].LineStyle =
WdLineStyle.wdLineStyleSingle;
tbl.Borders[WdBorderType.wdBorderBottom].Color = WdColor.wdColorBlack; tbl.Rows.Alignment = WdRowAlignment.wdAlignRowCenter; tbl.AutoFitBehavior(WdAutoFitBehavior.wdAutoFitWindow);
On re-reading... To insert at the current position - if you mean where the cursor is:
Word.Range rngSel = wdApp.Selection.Range;
rngSel.Tables.Add(//params here);
Otherwise, if you mean at the end of the information being inserted byy the code, instead of these two lines
rng.InsertBefore("List");
rng.Font.Name = "Verdana";
rng.Font.Size = 16;
rng.InsertParagraphAfter();
rng.InsertParagraphAfter();
rng.SetRange(rng.End, rng.End);
Use
rng.Text = "List\n\n"
rng.Font.Name = "Verdana";
rng.Font.Size = 16;
rng.Collapse(WdCollapseDirection.wdCollapseEnd);
\n inserts a new paragraph (carriage return) and can be included as part of a string.
Assigning text directly to the Range and working with the Collapse method is (in my opinion) a bit more predictable than the various Insert methods. Some of the Insert methods include what's inserted in the range, others do not.
FWIW when it's not clear what the problem might be it can help to put a rng.Select(); at a key point in the code and comment out the rest of the lines so that the code ends with the range in question visible. That can often be informative as to the origin of a problem with a range.

Pdf form fields position retrieval with Itext

With iTextSharp, I can retrieve all the form fields that are present in a PDF form.I'm using Adobe acrobat reader to edit the PDF, where I see, every field have a position attribute which denotes where the PDF field will reside in a form.
So my question is, can I read that value ?
For example if I have a form field Name in a PDF form, can I get the position value of this field, like left 0.5 inches, right 2.5 inches, top 2 inches, bottom 2 inches ?
Right now I'm retrieving the form fields with the below code :
string pdfTemplate = #"D:\abc.pdf";
PdfReader reader = new PdfReader(pdfTemplate);
var fields = reader.AcroFields;
int ffRadio = 1 << 15; //Per spec, 16th bit is radio button
int ffPushbutton = 1 << 16; //17th bit is push button
int ff;
//Loop through each field
foreach (var f in fields.Fields)
{
String type = "";
String name = f.Key.ToString();
String value = fields.GetField(f.Key);
//Get the widgets for the field (note, this could return more than 1, this should be checked)
PdfDictionary w = f.Value.GetWidget(0);
//See if it is a button-like object (/Ft == /Btn)
if (!w.Contains(PdfName.FT) || !w.Get(PdfName.FT).Equals(PdfName.BTN))
{
type = "T";
}
else
{
//Get the optional field flags, if they don't exist then just use zero
ff = (w.Contains(PdfName.FF) ? w.GetAsNumber(PdfName.FF).IntValue : 0);
if ((ff & ffRadio) == ffRadio)
{
//Is Radio
type = "R";
}
else if (((ff & ffRadio) != ffRadio) && ((ff & ffPushbutton) != ffPushbutton))
{
//Is Checkbox
type = "C";
}
else
{
//Regular button
type = "B";
}
}
//MessageBox.Show(type + "=>" + name + "=>" + value);
FormFields fld = new FormFields(name, type, value, "inputfield" +form_fields.Count);
form_fields.Add(fld);
if (type.Equals("T"))
addContent(form_fields.Count);
}
I was about to close this question as a duplicate of Find field absolute position and dimension by acrokey but that's a Java answer, and although most developers have no problem converting the Java to C#, it may be helpful for some developers to get the C# answer.
Fields in a PDF are visualized using widget annotations. One field can correspond with different of those annotations. For instance, you could have a field named name that is visualized on every page. In this case, the value of this field would be shown on every page.
There's a GetFieldPositions() method that returns a list of multiple positions, one for every widget annotations.
This is some code I copied from the answer to the question iTextSharp GetFieldPositions to SetSimpleColumn
IList<AcroFields.FieldPosition> fieldPositions = fields.GetFieldPositions("fieldNameInThePDF");
if (fieldPositions == null || fieldPositions.Count <= 0) throw new ApplicationException("Error locating field");
AcroFields.FieldPosition fieldPosition = fieldPositions[0];
left = fieldPosition.position.Left;
right = fieldPosition.position.Right;
top = fieldPosition.position.Top;
bottom = fieldPosition.position.Bottom;
If one field corresponds with one widget annotation, then left, right, top, and bottom will give you the left, right, top and bottom coordinate of the field. The width of the field can be calculated like this: right - left; the height like this: top - bottom. These values are expressed in user units. By default there are 72 user units in one inch.
If your document contains more than one page, then fieldPosition.page will give you the page number where you'll find the field.
All of this is documented on http://developers.itextpdf.com/

How can you interact with a copied+pasted Table using Microsoft Office Word Interop? Pasted table doesn't add to document.Tables.count

I'm copying and pasting a table using the following code:
Word.Table tableTemplate = document.Tables[tableNumber];
tableTemplate.Select();
word.Selection.Copy(); //word is my Word.Application
word.Selection.MoveDown(Word.WdUnits.wdLine, 2);
word.Selection.PasteAndFormat(Word.WdRecoveryType.wdTableOriginalFormatting);
table = document.Tables[tableNumber + 1];
Unfortunately, the document.Tables.Count variable isn't incremented when the table is pasted, and the last line throws an index out of bounds error. I'm sure it's something minor I'm missing.
In case anyone having a similar problem is looking for a solution, I placed a bookmark below the table, moved the selection cursor there, then selected the range just before the bookmark and pasted.
Word.Bookmark bkmrk = document.Bookmarks["MyBkmrk"];
Word.Range rng = document.Range(bkmrk.Range.Start - 1, bkmrk.Range.Start - 1);
rng.Select();
word.Selection.Paste();
This seemed to work infinitely better than trying to use MoveDown. I'd even tried using MoveDown in the selection by using the number of paragraphs in the range to determine how far, and that entirely didn't work.
Edit:
So, my real issue was that I needed to copy a table and paste some number of them in a loop, then edit the contents of the table. I kept running into the table pasting into itself and just generally being messed up. For anyone who needs to do something similar, here's some help:
Word.Table table = document.Tables[tableNumber];
table.Select();
wordApplication.Selection.Copy();
for(int i = 0; i < tablesINeed; i++)
{
Word.Range rng = document.Range(document.Tables[tableNumber + i].Range.End + 1, document.Tables[tableNumber + i].Range.End + 1);
rng.Select();
wordApplication.Selection.Paste();
// Modify table accordingly
}
It looks simple, but it took a lot of trial and error. Hopefully it will help someone.
I have an answer based on cboler's. I had problems if there is already something behind the table to be copied.
private static List<Table> CloneTables(Application Application, Document doc, int tableNumber, int tablesINeed)
{
List<Table> sameTables = new List<Table>();
Table lastTable = doc.Tables[tableNumber];
sameTables.Add(lastTable);
lastTable.Select();
Application.Selection.Copy();
for (int i = 0; i < tablesINeed; i++)
{
lastTable.Range.Next().InsertParagraphAfter();
Range rng = doc.Range(doc.Tables[tableNumber + i].Range.End + 1, doc.Tables[tableNumber + i].Range.End + 1);
rng.Select();
Application.Selection.Paste();
lastTable = doc.Tables[tableNumber + i + 1];
sameTables.Add(lastTable);
}
return sameTables;
}

Get Paragraph in Word document

I create a table of figures programmatically in a Word document.
Well, the ToF style is centered and I would like it to be left indent.
To do so (set paragraph indention) I have to get the paragraph where the ToF is located.
This is the way rto access the ToF:
wordApp.ActiveDocument.TablesOfFigures[1]
Any ideas?
Try the code below. Assuming that TablesOfFigures[1] is exists (otherwise we will get buffer overflow).
// Check in which paragraph TablesOfFigures[1] is found
for (int i=1; i <= wordApp.ActiveDocument.Paragraphs.Count; i++)
{
if (IsInRange(wordApp.ActiveDocument.TablesOfFigures[1].Range, wordApp.ActiveDocument.Paragraphs[i].Range))
{
MessageBox.Show("ToF is in paragraph " + i);
}
}
// Returns true if 'target' is contained in 'source'
private bool IsInRange(Range target, Range source)
{
return target.Start >= source.Start && target.End <= source.End;
}
Provided that you only have one Table of Figures, you can try this:
With wordApp.ActiveDocument.TablesOfFigures(1).Range
'Setting the indent
.ParagraphFormat.LeftIndent = CentimetersToPoints(1)
End With
I have tested it just using Word and it Selects the Table of Figures and then Indents it by 1cm

Categories