I'm trying to automate the creation of "Show Detail" of a Pivot Table from Excel using C#
using Excel = Microsoft.Office.Interop.Excel;
My code:
var excelApp = new Excel.Application();
excelApp.Visible = true;
Excel.Workbook workbook = excelApp.Workbooks.Open(#"D:\path_to_excel_file.xlsm");
Excel.Worksheet worksheet = (Excel.Worksheet)workbook.Worksheets["Sheet_Name"];
Excel.PivotTable pivot = worksheet.PivotTables("defined_pivot_table_name");
pivot.DataBodyRange.ShowDetail = true;
The code works, but it displays only the details of the first "Total" value. But what I want is to get the "Show Details" of the "Grand Total".
In this case for this pivot table, it will create a new sheet with the 4 elements, but what I want is for the Grand Total (202).
I've tried selecting it first by pivot.PivotSelect("Grand Total"); but still no results. I've also checked pivot.RowGrand and pivot.ColumnGrand which both return True.
If your pivottable has both, RowGrand AND ColumnGrand, then it also has a grand total
if (pivot.RowGrand && pivot.ColumnGrand)
Then you can use the last cell of the pivottable's TableRange1 to generate the details by ShowDetail
int countRows = pivot.TableRange1.Rows.Count;
int countColumns = pivot.TableRange1.Columns.Count;
pivot.TableRange1.Cells[countRows, countColumns].ShowDetail = true;
Maybe this can help
// Access the pivot table by its name in the collection.
PivotTable pivotTable = worksheet.PivotTables["PivotTable1"];
// Access the pivot field by its name in the collection.
PivotField field = pivotTable.Fields["Category"];
// Display multiple subtotals for the field.
field.SetSubtotal(PivotSubtotalFunctions.Sum | PivotSubtotalFunctions.Average);
// Show all subtotals at the bottom of each group.
pivotTable.Layout.ShowAllSubtotals(false);
// Hide grand totals for rows.
pivotTable.Layout.ShowRowGrandTotals = False
// Hide grand totals for columns.
pivotTable.Layout.ShowColumnGrandTotals = False
// custom label for grand totals
pivotTable.View.GrandTotalCaption = "Total Sales";
Related
In C# I have populated an excel file with data. Now I would like to create a table starting at cell A2.
I am using the code below to create the table but instead of creating the table starting at Cell A2, the table is being created starting at cell A3
using var package = new ExcelPackage(file);
var ws = package.Workbook.Worksheets.Add(Name: "MainReport");
var range = ws.Cells[Address: "A2"].LoadFromCollection(people, PrintHeaders: true);
range.AutoFitColumns();
//Formats Header row
ws.Cells[Address: "A1"].Value = "My Data!";
ws.Cells[Address: "A1:C1"].Merge = true;
ws.Column(col: 1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
ws.Row(row: 1).Style.Font.Size = 24;
ws.Row(row: 1).Style.Font.Color.SetColor(Color.Pink);
ws.Row(row: 2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
ws.Row(row: 2).Style.Font.Bold = true;
ws.Column(col: 3).Width = 20;
//create a range for the table
ExcelRange range_table = ws.Cells[2, 1, ws.Dimension.End.Row, ws.Dimension.End.Column];
//add a table to the range
ExcelTable tab = ws.Tables.Add(range_table, "Table1");
//format the table
tab.TableStyle = TableStyles.Medium2;
Excel itself works so that if you select A2:C4 and create a table and you say "has no Headers" it will put some generic headers in A2:C2 and shift the data area (and the data if there is any) to A2:C5.
If you say "has Headers" the headers will be defined in A2:C2, too, but the data only is in A3:C4.
The real Excel obviously does some guessing whether headers are present or not and pre-ticks the box for you.
As stated here in the API Documentation, there is no flag to define if headers are present in the range so there might be no guessing and it might be assumed it is "no" and the shifting as explained on top takes place.
If you take that into consideration you should be able to work it out.
I would like to add a column that already contains cells values between two columns (or at the end) of a worksheet of an existing workbook that I load.
So I have a function that sets that "column values" I need :
private static Workbook SetIndicatorsWorkbook()
{
var workbook = new Workbook(WorkbookFormat.Excel2007MacroEnabled);
var worksheet = workbook.Worksheets.Add("Unit & Integration Tests");
//Don't worry about team and jenkinsBuilTeams variables
foreach (var team in jenkinsBuildTeams)
{
worksheet.Rows[posX].Cells[0].Value = lastnbUnitTests + lastnbIntegrationTests;
posX += 1;
}
return workbook;
}
And then in main function I want to add this column (which is workbook.worksheets[0].Columns[0] ) in a loaded workbook :
private static void Main()
{
//The workbook I need to update
Workbook workbook = Workbook.Load("file.xlsx");
Workbook temp = SetIndicatorsWorkbook();
WorksheetColumn wc = temp.Worksheets[0].Columns[0];
//The issue is that Worksheet's Columns collection has no "Insert" property
workbook.Save("file.xlsx");
}
The Columns collection of the Worksheet has an Insert method that will shift data/formatting just as would happen in Excel. This was added in the 2014 volume 2 version. You can read more about that in the help topic or the api documentation. Note I've linked to the WPF version help but the Insert method is available in the other platforms as well.
What is the C# code to Display Excel Pivot Table layout in Tabular Form?
Here is the code from the Macro that I need converted to C#:
ActiveSheet.PivotTables("PivotTable1").RowAxisLayout xlTabularRow
Here is my starting code to show what variables I have set up:
//Excel.Application excel = new Excel.Application();
activeWorkBook = excel.ActiveWorkbook;
sheets = excel.Sheets;
pivotWorkSheet = (Excel.Worksheet)sheets.Add();
// Create the Pivot Table
pivotCaches = activeWorkBook.PivotCaches();
pivotCache = pivotCaches.Create(Excel.XlPivotTableSourceType.xlDatabase,"Sheet1!$A$2:$J$35");
pivotTable = pivotCache.CreatePivotTable("Sheet2!R2C1");
// Set the Pivot Fields
pivotFields = (Excel.PivotFields)pivotTable.PivotFields();
I figured it out in case anyone else is looking for this I used:
pivotTable.RowAxisLayout(Excel.XlLayoutRowType.xlTabularRow);
I have a predefined Excel workbook with all sheets in place and I need to write content to it. I succesfully write to cells.
The problem is in a particular worksheet that i need to add three columns to it. In the code bellow, first i'm grabbing the Worksheet and then i proceed to add columns. This code runs fine, i mean, no exception is thrown, but then I get an error when I try to open the Excel file, stating that there are some content that cannot be read and all the content of this particular worksheet is cleared.
I know that the problem is with this operation because if I comment out those lines that add columns, the workbook opens just fine with all the cells values I write from code in place.
This is the relevant code, for testing purpose I'm trying to add 3 columns:
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)){
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single( s => s.Name == "Miscellaneous Credit" );
Worksheet workSheet2 = ( (WorksheetPart)document.WorkbookPart.GetPartById( sheet2.Id ) ).Worksheet;
Columns cs = new Columns();
for ( var y = 1; y <= 3; y++ ) {
Column c = new Column()
{
Min = (UInt32Value)1U,
Max = (UInt32Value)1U,
Width = 44.33203125D,
CustomWidth = true
};
cs.Append( c );
}
workSheet2.Append( cs );
}
EDIT : As per Chris's explanation about columns's concept
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)){
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single( s => s.Name == "Miscellaneous Credit" );
Worksheet workSheet2 = ( (WorksheetPart)document.WorkbookPart.GetPartById( sheet2.Id ) ).Worksheet;
// Check if the column collection exists
Columns cs = workSheet2.Elements<Columns>().FirstOrDefault();
if ( ( cs == null ) ) {
// If Columns appended to worksheet after sheetdata Excel will throw an error.
SheetData sd = workSheet2.Elements<SheetData>().FirstOrDefault();
if ( ( sd != null ) ) {
cs = workSheet2.InsertBefore( new Columns(), sd );
}
else {
cs = new Columns();
workSheet2.Append( cs );
}
}
//create a column object to define the width of columns 1 to 3
Column c = new Column
{
Min = (UInt32Value)1U,
Max = (UInt32Value)3U,
Width = 44.33203125,
CustomWidth = true
};
cs.Append( c );
}
This first part of answer deals about how to set columns width (based on the initial sample code, I was thinking that you wanted only define the width of the columns).
First, it seems you misunderstood what are Min and Max properties of the Column object. They represent respectively First and Last column affected by this 'column info' record. So if you have a set of contiguous columns with the same width, you can set that width using one Column class. In your snippet you define 3 times the width of the same column (Index 1).
Then, you presume Columns collection doesn't exist yet...
And finally, the main point is that if the Columns collection is appended after SheetData, Excel will throw error.
Final code that work for me (Open XML SDK 2.0)
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)) {
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single(s => s.Name == "Your sheet name");
Worksheet workSheet2 = ((WorksheetPart)document.WorkbookPart.GetPartById(sheet2.Id)).Worksheet;
// Check if the column collection exists
Columns cs = workSheet2.Elements<Columns>().FirstOrDefault();
if ((cs == null)) {
// If Columns appended to worksheet after sheetdata Excel will throw an error.
SheetData sd = workSheet2.Elements<SheetData>().FirstOrDefault();
if ((sd != null)) {
cs = workSheet2.InsertBefore(new Columns(), sd);
} else {
cs = new Columns();
workSheet2.Append(cs);
}
}
//create a column object to define the width of columns 1 to 3
Column c = new Column {
Min = (UInt32Value)1U,
Max = (UInt32Value)3U,
Width = 44.33203125,
CustomWidth = true
};
cs.Append(c);
}
I'm still confused on how to perform column insert. Says I have
columns A, B and C, I want to insert three columns between B and C,
ending up with columns A,B,C,D,E,F. How can i achieve it?
The Columns object in OpenXml SDK is here to store styles and width informations for the columns. Inserting a Column in the collection won't "insert" a column in the sheet.
"Inserting" a column like you mean is a very large and complex task with OpenXmlSDK.
From my understanding of the problem, it means you will have to find all cells and shift them by changing their reference (ex. a cell with ref "B1" would become "F1" after inserting 3 columns, etc ...). And it means you will have to change a lot of other things (reference of cell in formulas for example).
This kind of task could be easily done with Office.Interop or probably with libraries like EEPlus or ClosedXml.
Do you know an equivalent to VBA code:
Range(Selection, Selection.End(xlToRight)).Select
In Aspose.Cells. It seems that its only possible to select the last cell in the entire row:
public Aspose.Cells.Cell EndCellInRow ( Int32 rowIndex )
Or the last cell on the right within a range:
public Aspose.Cells.Cell EndCellInRow ( Int32 startRow, Int32 endRow, Int32 startColumn, Int32 endColumn )
but then you must know more or less how big your table is going to be.
I found this from 2009: http://www.aspose.com/community/forums/permalink/196519/196405/showthread.aspx but that will not resolve my problem as I may have many tables in a sheet both horizontally and vertiacally. And I can't predict where they are going to be.
Edit1:
Sorry if this is dumb question, but ctrl+shift+arrow is such a common operation that I can't believe it would be not implemented so I'm making sure I really have to re-invent the wheel.
Aspose.Cells provides the list of tables in a worksheet using property named 'Worksheet.ListObjects'. 'ListObjects' is a colloection of 'ListObject' type which represents a Table in an excel sheet. That means if one has more than one Tables in a worksheet, the ListObjects collection will give access to every table in the worksheet very conveniently. Each 'ListObject' in turn contains a property named 'DataRange' which specifies all the cells inside a Table. For the sake of convenience DataRange can be used for following operations on a Table:
To apply styles/formatting on the cells in Table
To get the data values
Merge or move the cells in Range
Export contents
To get enumerator to traverse through Table cells
To make selection of cells from DataRange, you can traverse using DataRange to get all the cells in a Row (This could also be done for a column)
Applying any operation on Table cells like after selecting cells using Ctrl+Shift+Arrow, could be performed using a workbook object as follows:
Workbook workbook = new Workbook(new FileStream("book1.xls", FileMode.Open));
if (workbook.Worksheets[0].ListObjects.Count > 0)
{
foreach (ListObject table in workbook.Worksheets[0].ListObjects)
{
Style st = new Style();
st.BackgroundColor = System.Drawing.Color.Aqua;
st.ForegroundColor = System.Drawing.Color.Black;
st.Font.Name = "Agency FB";
st.Font.Size = 16;
st.Font.Color = System.Drawing.Color.DarkRed;
StyleFlag stFlag = new StyleFlag();
stFlag.All = true;
table.DataRange.ApplyStyle(st, stFlag);
}
}
workbook.Save("output.xls");
There is also some worthy information available in Aspose docs about Table styles and applying formatting on a ListObject. For getting last Table cell in a certain row or column, I am sure this will help:
int iFirstRowIndex = table.DataRange.FirstRow;
int iFirstColumnIndex = table.DataRange.FirstColumn;
int iLastRowIndex = table.DataRange.RowCount + iFirstRowIndex;
int iLastColumnIndex = table.DataRange.ColumnCount + iFirstColumnIndex;
for (int rowIndex = 0; rowIndex < table.DataRange.RowCount; rowIndex++)
{
//Get last cell in every row of table
Cell cell = worksheet.Cells.EndCellInColumn(rowIndex + iFirstRowIndex, rowIndex + iFirstRowIndex, (short)iFirstColumnIndex, (short)(iLastColumnIndex - 1));
//display cell value
System.Console.WriteLine(cell.Value);
}