Adding tables results in nested tables - c#

I am attempting to add consecutive tables to a word document from C# (VS 2013, .NET 4.5, Word 2013) and the code below nests the second table within the first table. I am attempting to create distinct tables that are added between two bookmarks (I have tested this with a range defined as a single bookmark as well).
I own the code and source/destination document format, so i can make any changes as needed, however the content is all dynamic, so i can't just create a pre-defined bookmark for each table.
A subset of the code that is representative of my issue is below:
Word.Application oWord = new Word.Application();
Word.Document oTargetWordDoc = oWord.Documents.Open(WordTemplateFile);
object oBookmarkBegin = "bookmark_begin";
object oBookmarkEnd = "bookmark_end";
object oBookmarkBeginRange = oTargetWordDoc.Bookmarks.get_Item(ref oBookmarkBegin).Range.Start;
object oBookmarkEndRange = oTargetWordDoc.Bookmarks.get_Item(ref oBookmarkEnd).Range.Start;
Word.Range oRange = oTargetWordDoc.Range(ref oBookmarkBeginRange, ref oBookmarkEndRange);
Word.Table wtTable1 = oRange.Tables.Add(oRange, 1, 2);
wtTable1.Cell(1,1).Range.Text = "t1/r1/c1";
wtTable1.Cell(1,2).Range.Text = "t1/r1/c2";
Word.Table wtTable2 = oRange.Tables.Add(oRange, 1, 2);
wtTable2.Cell(1,1).Range.Text = "t2/r1/c1";
wtTable2.Cell(1,2).Range.Text = "t2/r1/c2";
// other goo/close/save/etc...
The output looks like this:
t1/r1/c1|t1/r1/c2
t2/r1/c1|t2/r1/c2| <-- nested within column 1 in the first table (notice trailing | marking beginning of first table's second column)
Clearly, I would like two distinct tables, without nesting, resulting in:
t1/r1/c1|t1/r1/c2
t2/r1/c1|t2/r1/c2
Any assistance in this matter would be greatly appreciated!

Related

How can I programmatically add a ReportFilter to a PivotTable (Excel Interop/C#)?

This is a more specific version of the question here.
I am able to generate a PivotTable using this code:
private void AddPivotTable()
{
pivotData = _xlSheet.Range["A1:K1600"];
pivotDestination = _xlSheet.Range["A1602", useDefault];
_xlBook.PivotTableWizard(
Excel.XlPivotTableSourceType.xlDatabase,
pivotData,
pivotDestination,
pivotTableName,
true,
true,
true,
true,
useDefault,
useDefault,
false,
false,
Excel.XlOrder.xlDownThenOver,
0,
useDefault,
useDefault
);
// Set variables used to manipulate the Pivot Table.
pivotTable = (Excel.PivotTable)_xlSheet.PivotTables(pivotTableName);
shortnamePivotField = (Excel.PivotField)pivotTable.PivotFields(2);
itemcodePivotField = (Excel.PivotField)pivotTable.PivotFields(3);
descriptionPivotField = (Excel.PivotField)pivotTable.PivotFields(4);
priceweekPivotField = (Excel.PivotField)pivotTable.PivotFields(5);
weekPivotField = (Excel.PivotField)pivotTable.PivotFields(6);
pricePivotField = (Excel.PivotField)pivotTable.PivotFields(7);
regionorderPivotField = (Excel.PivotField)pivotTable.PivotFields(10);
// Format the Pivot Table.
pivotTable.Format(Excel.XlPivotFormatType.xlReport2);
pivotTable.InGridDropZones = false;
pivotTable.SmallGrid = false;
pivotTable.ShowTableStyleRowStripes = true;
pivotTable.TableStyle2 = "PivotStyleLight1";
// Page Field
shortnamePivotField.Orientation = Excel.XlPivotFieldOrientation.xlPageField;
// Row Fields
descriptionPivotField.Orientation = Excel.XlPivotFieldOrientation.xlRowField;
itemcodePivotField.Orientation = Excel.XlPivotFieldOrientation.xlRowField;
// Data Fields
pricePivotField.Orientation = Excel.XlPivotFieldOrientation.xlDataField;
pricePivotField.Function = Excel.XlConsolidationFunction.xlSum;
}
What I want is to be able to create a PivotTable something like this:
...(which I generated using Excel manually), but as you can see here:
...trying to accomplish it with the code above is not working. What is needed to programmatically add a ReportFilter to my PivotTable?
UPDATE
I found a PivotFilter class, but my probes at testing it out:
// Pivot Filter
//shortnamePivotFilter.DataField = shortnamePivotField; <= DataField readonly
//shortnamePivotFilter.MemberPropertyField = shortnamePivotField; ""
//shortnamePivotFilter.PivotField = shortnamePivotField; ""
...all failed, because PivotFilter's most likely-seeming member properties (DataField, MemberPropertyField, and PivotField) are all readonly.
UPDATE 2
With this:
//shortnamePivotField.Orientation = Excel.XlPivotFieldOrientation.xlPageField;
. . .
shortnamePivotField.Orientation = Excel.XlPivotFieldOrientation.xlColumnField;
(removing shortnamePivotField as a/the PageField and adding it as a ColumnField) I get this:
Closer, but no "Seeger" (Turn, turn, turn) yet.
UPDATE 3
There doesn't seem to be much rhyme, reason, or logic to how things are plopped into the PivotTable. I've got ShortName as a Category Field, Price as a Data Field, and both Description and ItemCode as Row Fields, yet "PRICE" appears as a label in the NW corner (why?) as well as in the ShortName columns (whichever ones are selected from the "CheckedListBox" associated with "SHORT NAME"), while Description and ItemCode are both filterable column headings (like SHORT NAME).
Okay, the Row Fields (Description and ItemCode) determine which rows display, and the Column field determines which Short Name values appear as columns (makes sense), but:
(a) Why is the "PRICE" label vagranting up there in Victoria, British Columbia?
-and:
(b) Why is there a duplicated "Total" row beneath each row? Since each description has its own row, having a Total for that Description simply takes up space without adding any value, eerily reminiscent of congressmen.
UPDATE 4
I recorded a macro and dragged "SHORT NAME" from the list in PivotTable Field List.Choose fields to add to report to the Report Filter box in the NW quadrant below it.
The macro recorded as:
Sub DragShortNameToReportFilter()
With ActiveSheet.PivotTables("Pivot Test").PivotFields("SHORT NAME")
.Orientation = xlPageField
.Position = 1
End With
ActiveWindow.SmallScroll Down:=-6
ActiveSheet.PivotTables("Pivot Test").PivotFields("SHORT NAME").CurrentPage = _
"(All)"
End Sub
...so I tried this C# code:
shortnamePivotField.Orientation = Excel.XlPivotFieldOrientation.xlColumnField;
shortnamePivotField.Position = 1;
shortnamePivotField.CurrentPage = "(All)";
...and this is the result:
So it worked pretty well as far as it went - but my Row Fields (Description and ItemCode) and Data Field (Price) are now gone or at best invisible/mute.
While the spreadsheet is being created, it now prompts me with, "Do you want to replace the contents of the destination cells in [Book1]Sheet1?" (I respond with "OK" but am not overly thrilled with it, not knowing what data I may be thereby replacing).
UPDATE 5
So I started from "scratch" and first removed all the contents of the boxes (Report Filter, Row Labels, Values) and then added them back with the Macro Recorder faithfully recording. It told me I did this:
With ActiveSheet.PivotTables("Pivot Test").PivotFields("SHORT NAME")
.Orientation = xlPageField
.Position = 1
End With
With ActiveSheet.PivotTables("Pivot Test").PivotFields("DESCRIPTION")
.Orientation = xlRowField
.Position = 1
End With
With ActiveSheet.PivotTables("Pivot Test").PivotFields("ITEM CODE")
.Orientation = xlRowField
.Position = 2
End With
ActiveSheet.PivotTables("Pivot Test").AddDataField ActiveSheet.PivotTables( _
"Pivot Test").PivotFields("PRICE"), "Sum of PRICE", xlSum
...which doesn't look like much more than I already have in code. I just added the Position values for the two RowFields so that code now looks like this:
// Row Fields
descriptionPivotField.Orientation = Excel.XlPivotFieldOrientation.xlRowField;
descriptionPivotField.Position = 1;
itemcodePivotField.Orientation = Excel.XlPivotFieldOrientation.xlRowField;
itemcodePivotField.Position = 2;
...but, while better, it's still not quite as nifty as the auto-generated PivotTable...

C# Nested Tables in Word

I'm trying to design a table that has 3 additional tables in the last cell. Like this.
I've managed to get the first nested table into row 4, but my second nested table is going into cell(1,1) of the first table.
var wordApplication = new Word.Application();
wordApplication.Visible = true;
var wordDocument = wordApplication.Documents.Add();
var docRange = wordDocument.Range();
docRange.Tables.Add(docRange, 4, 1);
var mainTable = wordDocument.Tables[1];
mainTable.set_Style("Table Grid");
mainTable.Borders.Enable = 0;
mainTable.PreferredWidthType = Word.WdPreferredWidthType.wdPreferredWidthPercent;
mainTable.PreferredWidth = 100;
docRange.Collapse(Word.WdCollapseDirection.wdCollapseStart);
var phoneRange = mainTable.Cell(4, 1).Range;
phoneRange.Collapse(Word.WdCollapseDirection.wdCollapseStart);
phoneRange.Tables.Add(phoneRange, 3, 2);
var phoneTable = mainTable.Cell(4, 1).Tables[1];
phoneTable.set_Style("Table Grid");
phoneTable.Borders.Enable = 0;
phoneTable.AutoFitBehavior(Word.WdAutoFitBehavior.wdAutoFitContent);
phoneTable.Rows.RelativeHorizontalPosition = Word.WdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin;
phoneRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
I've tried collapsing the range, adding in a paragraph then collapsing the range again. No luck. I found this post and many similar ones, but I must be missing something.
Thanks for your time.
It usually helps in situation like these to add a line in your code: phoneRange.Select(); and having code execution end with that. Take a look at where the Range actually is. Now you can test using the keyboard where the Range needs to be in order to insert the next table successfully.
Since you say phoneRange selects outside the third row, rather than working with phoneRange try setting a new Range object to phoneTable.Range then collapse it to its end-point.

Get the formatting of a table that a specific string of text exisits in and create a new table with the same formatting

Using OpenXML in C#, we need to:
Find a specific string of text on a Word document (this text will always exist in a table cell)
Get the formatting of the text and the table that the text exists in.
Create a new table with the same text and table formatting while pulling in text values for the cell from a nested List
This is the code that I currently have and the places I am not sure how do:
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(fileWordFile, true))
{
MainDocumentPart mainPart = wordDoc.MainDocumentPart;
Body body = mainPart.Document.Body;
IEnumerable paragraphs = body.Elements<Paragraph>();
Paragraph targetParagraph = null;
//Comment 1: Loop through paragraphs and search for a specific string of text in word document
foreach (Paragraph paragraph in paragraphs) {
if(paragraph.Elements<Run>().Any()) {
Run run = paragraph.Elements<Run>().First();
if(run.Elements<Text>().Any()) {
Text text = run.Elements<Text>().First();
if (text.Text.Equals("MY SEARCH STRING")) {
targetParagraph = paragraph;
// Comment 2: How can I get the formatting of the table that contains this text??
}
}
}
}
//Comment 3: Create table with same formatting as where the text was found
Table table1 = new Table();
TableProperties tableProperties1 = new TableProperties();
//Comment 4: How can I set these properties to be the same as the one found at "Comment 2"??
wordDoc.Close();
wordDoc.Dispose();
}
If you're looking for text elements that are inside a table cell, you can use a LINQ query to get there quickly without needing to use a heap of nested loops.
// Find the first text element matching the search string
// where the text is inside a table cell.
var textElement = body.Descendants<Text>()
.FirstOrDefault(t => t.Text == searchString &&
t.Ancestors<TableCell>().Any());
Once you have your match, the easiest way to duplicate the containing table with all its formatting and contents is simply to clone it.
if (textElement != null)
{
// get the table containing the matched text element and clone it
Table table = textElement.Ancestors<Table>().First();
Table tableCopy = (Table)table.CloneNode(deep: true);
// do stuff with copied table (see below)
}
After that, you can add things to the corresponding cell of the copied table. It's not entirely clear what you meant by "pulling in text values for the cell from a nested List" (what list? nested where?), so I'll just show a contrived example. (This code would replace the "do stuff" comment in the code above.)
// find the table cell containing the search string in the copied table
var targetCell = tableCopy.Descendants<Text>()
.First(t => t.InnerText == searchString)
.Ancestors<TableCell>()
.First();
// get the properties from the first paragraph in the target cell (so we can copy them)
var paraProps = targetCell.Descendants<ParagraphProperties>().First();
// now add new stuff to the target cell
List<string> stuffToAdd = new List<string> { "foo", "bar", "baz", "quux" };
foreach (string item in stuffToAdd)
{
// for each item, clone the paragraph properties, then add a new paragraph
var propsCopy = (ParagraphProperties)paraProps.CloneNode(deep: true);
targetCell.AppendChild(new Paragraph(propsCopy, new Run(new Text(item))));
}
Lastly, you need to add the copied table to the document somewhere or you won't see it. You don't say in your question where you would want this to appear, so I'll just put it at the end of the document. You can use methods like InsertAfter, InsertAt, InsertBefore, etc. to insert the table relative to other elements.
body.AppendChild(tableCopy);
Hope this helps.

Using OpenXML, how can I associate a list for data validation

I am processing an .xlsm file and need to know how to use a list on another sheet for data validation using openXML and C#.
To start, I have a .xlsm file with two empty sheets and macros in it. In my program I open the file, Create the column header on Sheet1 then create the validation list on sheet2. So, after I run my program Sheet1 "A1" contains the text "Color" and Sheet2 "A1:A4" contains "Blue","Green","Red","Yellow". I get this far just fine.
I would like to make it so there is a dropdown list in all cells of column "A" on sheet1 that contains each of the 4 colors and enforces them as the only input. In Microsoft Excel this is done by going to the "Data" tab, selecting "Data Validation" selecting "List" and highlighting the cells you want to use. I need to make this association programmatically.
The (Desired) XML that Microsoft Excel creates if I do it manually is this:
<extLst>
<ext uri="{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main">
<x14:dataValidations count="1" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main">
<x14:dataValidation type="list" allowBlank="1" showInputMessage="1" showErrorMessage="1">
<x14:formula1>
<xm:f>'Validation Data'!$A$1:$A$4</xm:f>
</x14:formula1>
<xm:sqref>A1:A1048576</xm:sqref>
</x14:dataValidation>
</x14:dataValidations>
</ext>
</extLst>
The following method and results is something I tried. It may give a better Idea of what I'm trying to do.
Here, I pass in "'Sheet2'!$A$1:$A$4" as the "validationListCells" parameter. This represents the cells in "Sheet2" that, in this example, would contain the color names "Red", "Green"...etc.
I pass in "A2:A1048576" as the "cellsToValidate" parameter. This represents all cells of Sheet1 column "A", on which I want to enforce validation.
I pass "Sheet1" as the worksheetName parameter.
private void InsertValidation(String worksheetName, String validationListCells, String cellsToValidate)
{
DataValidations dataValidations1 = new DataValidations() { Count = (UInt32Value)1U };
DataValidation dataValidation1 = new DataValidation()
{
Formula1 = new Formula1(validationListCells),
Type = DataValidationValues.List,
ShowInputMessage = true,
ShowErrorMessage = true,
SequenceOfReferences = new ListValue<StringValue>() { InnerText = cellsToValidate }
};
dataValidations1.Append(dataValidation1);
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(_documentPath, true))
{
WorksheetPart worksheetPart = GetWorksheetPartByName(spreadSheet, worksheetName);
worksheetPart.Worksheet.Append(dataValidations1);
worksheetPart.Worksheet.Save();
}
}
It results in this XML in Sheet1.xml. Which causes an error in Excel.
<x:dataValidations count="1">
<x:dataValidation type="list" showInputMessage="1" showErrorMessage="1" sqref="A2: A1048576">
<x:formula1>'Sheet2'!$A$1:$A$5</x:formula1>
</x:dataValidation>
</x:dataValidations>
It looks like I may be on the right track since it is beginning to resemble the xml created by Excel, but I'm completely new to openXML and I'm finding little about this topic on the net.
Thanks in advance!
For anyone else in need of this..the code below worked for me.
I put in there user3251089's variable names.
In general, when I try to programmatically create an excel "feature" I manually make a really basic excel that has in it that feature (delete extra sheets too). Then I reflect the code and try to make it prettier.
hope it serves to someone!
using Excel = DocumentFormat.OpenXml.Office.Excel;
using X14 = DocumentFormat.OpenXml.Office2010.Excel;
.....
Worksheet worksheet = worksheetPart.Worksheet;
WorksheetExtensionList worksheetExtensionList = new WorksheetExtensionList();
WorksheetExtension worksheetExtension = new WorksheetExtension() { Uri = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" };
worksheetExtension.AddNamespaceDeclaration("x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main");
X14.DataValidations dataValidations = new X14.DataValidations() { Count = (UInt32Value)3U };
dataValidations.AddNamespaceDeclaration("xm", "http://schemas.microsoft.com/office/excel/2006/main");
//sites validation
dataValidations.Append(new X14.DataValidation()
{
Type = DataValidationValues.List,
AllowBlank = true,
ShowInputMessage = true,
ShowErrorMessage = true,
DataValidationForumla1 = new X14.DataValidationForumla1() { Formula = new Excel.Formula(validationListCells) },
ReferenceSequence = new Excel.ReferenceSequence(cellsToValidate)
});
worksheetExtension.Append(dataValidations);
worksheetExtensionList.Append(worksheetExtension);
worksheet.Append(worksheetExtensionList);
worksheet.Save();

Word Automation in C#- Creating Tables in Word

I have a C# application which will open a word document and then replace some of the predefined bookmarks with the data which i have like Name,Class etc..
It is all are just string values .Now I want to render a Table with dynamic number of rows to the word document.I want the table in a particular place in the document.
Can i use bookmark for this.If so how ??? IS there any other method?
Yes, you can use bookmarks and also use fields to replace it with table with n no. of rows and n no. of columns.
You can loop through fields and get its range and using range you can add table at field location:
//CREATING OBJECTS OF WORD AND DOCUMENT
Word.Application oWord = new Word.Application();
Word.Document oWordDoc = new Word.Document();
foreach (Word.Field myMergeField in oWordDoc.Fields)
{
iTotalFields++;
Word.Range rngFieldCode = myMergeField.Code;
String fieldText = rngFieldCode.Text;
// ONLY GETTING THE MAILMERGE FIELDS
if (fieldText.StartsWith("tablename"))
{
myMergeField.Select();
oWordDoc.table.add(rngFieldCode,4//for rows,4// for colioulns,ref omising....);
}
}

Categories

Resources