Can ClosedXML create a formatted table from merged cells? - c#

Based on documentation examples from: https://github.com/ClosedXML/ClosedXML/wiki/Using-Tables
In a C# application, I use ClosedXML to create a workbook with new content. Part of the content is a range of cells that I would like to style as a table. However, the first "column" of the table actually spans multiple cell columns. Without applying any table formatting, the result is like the following simplified snippet.
In my C# application, after I have populated the cells of that table, I grab the range of those cells and create an IXLTable from them.
IXLWorkbook book = ...;
IXLWorksheet sheet = ...;
//------------------------------
// Populate cells with milestone data.
...
//------------------------------
// Create a table.
IXLRange range = sheet.Range( "A1:D3");
IXLTable table = range.CreateTable();
//------------------------------
// Done.
book.SaveAs( "foo.xlsx" );
That works, insofar as the C# application compiles, executes, and generates a workbook file.
However, when I open that workbook in Microsoft Excel, Excel decides that there are errors and prompts me whether to repair the workbook. Excel repairs the workbook by just eliminating any <table> that was in it.
In contrast, if the first "column" of the table just spans one cell column, then the created table has no errors and survives to be loaded in Excel. The result is like the following simplified snippet.
Therefore, the root problem is that the table contains merged cells.
Is there any way to accomplish what I want, with one of the table "columns" consisting of merged cells that span multiple cell columns?

IXLRange range = sheet.Range( "A1:D3");
range.Range(startRow, startColumn, startRow, endColumn).Merge();

Related

ExcelDataReader in C# - How to reference an individual Cell using row and column cordinates

I'm reading an .xlsx spreadsheet into a C# console app with a view to outputting the content as a formatted xml file (to be picked up by another part of the system further down the line).
The problem with the the .xslx file is that it's a pro-forma input document based on, and replacing, an old paper-based order form we used to provide to customers, and the input fields aren't organised as a series of similar rows (except in the lower part of the document which consists of up to 99 rows of order detail lines). Some of the rows in the header part of the form/sheet are a mixture of label text AND data; same with the columns.
Effectively, what I need to do is to be able to cherry pick data from the initial dozen or so rows in order to poke data into the xml structure; the latter part of the document I can process by iterating over the rows for the order detail lines.
I can't use Interop as this will end up as an Azure function - so I've used ExcelDataReader to convert the spreadsheet to a dataset, then convert that dataset to a new dataset entirely composed of string values. But I haven't been able to successfully point to individual cells as I had expected to be using syntax something like
var cellValue = MyDataSet.Cell[10, 2];
I'd be grateful for any advice as to how I might get the result I need.
A Dataset has Tables and those have Rows which hold ColumnValues
A WorkSheet transforms into a Table (with Columns) and the Cells transform to Rows and column values.
To find the cell value at [10,2] on the first Worksheet do:
var cellValue = MyDataSet.Tables[0].Rows[10][2];
Remember that cellValue will be of type object. Cast accordingly.

In ClosedXML, how to sort a worksheet by a column?

In C# using asp/MVC the app generates an Excel .xlsx file based on data thats filled in to the specific columns, works great.
But the goal is to provide additional worksheets that use the same columns but sort on specific colums, such as Column "J"
var wb = new XLWorkbook();
var ws = wb.Worksheets.Add("Proj Info");
var ws2 = wb.Worksheets.Add("Sort By Dates");
The worksheet ws has values filled in by variables or formulas, the data is correct, but cannot make it sort on a column
ws.AutoFilter.Column("J"); //no, nothing changes
ws.Column("J").Sort(); -> this shifts all the columns up but does not sort
ws.Column("J").Sort(XLSortOrder.Ascending); ->same, doesnt sort only shifts
Update: ws.Sort(9); worked in sorting, but the problem is that Column 10 has a Formula, and I need to sort on that Column.
ws.Cell("J" + c).FormulaR1C1 = "=C$2-F" + c;
With this? it WILL NOT SORT. The ws.Sort(10); works when the cell contains a final value, but when its got the Formula? Is there any workaround to force the Excel page to sort after its implemented the formula's in each cell?
You are not sorting the table, but the values in column J.
Try this:
ws.Sort("Column10");

Excel table validation / formula not being copied to new table row

I am currently using EPPlus library to export large amounts of data to several worksheets and tables in side each of those worksheets.
I have been able to create list validation and have it working via a lookup worksheet named range perfectly fine. However, I have come across some strange behaviour which I have been unable to figure out.
To begin:
I download the file. I open the file. I select a spreadsheet with a table, there are multiple rows in the table, there is a list validation column with Options Yes/No to select from a dropdown. Each row has this list validation.
Scenario 1:
I then create a new row in the excel table, by dragging from the bottom right corner of the excel table to create the new row. The formula was not copied to the new row. I have now lost the validation for a new row in my excel table.
Scenario 2:
I delete all existing rows in the excel table, except for the first row (which still contains list validation in the Yes/No column). I THEN create a new row in the excel table by dragging from the bottom right corner of the excel table to create the new row.
The formula IS copied to the new row, I can now insert new valid data into this row by using the provided validation.
The logic of my code:
Each cell has validation applied to it by a loop which gets the kind of validation the cell needs to have (i.e number, date, list, greater than, less than etc). List validation is accessed via a named table lookup address. There is NO XML output error and the file opens fine, I can access the list validation from the cells without any problem.
Things I have tried to fix this issue:
1) Fill the range of cells, THEN create the excel table from this range.
- The idea behind this is, to first have a selection of data created, then select the range and just turn it into an excel table. Default behaviour would be for new rows in a table to just copy the fomula from the row above. So this solution seems logical.
2) Create an excel table on a range of non-filled cells, then fill this range.
- The idea behind this is, there could have been a bug in the way EPPlus creates a table in the worksheet, or possibly there could be an issue with order of XML elements and really was simply just an experimental change.
The code:
var strategy = Strategy.CreateTableFirst;
ExcelRange subRowDataRange = null;
ExcelTable table = null;
if (strategy == Strategy.CreateTableFirst)
{
subRowDataRange = worksheet.Cells[headerRowIndex, worksheet.Dimension.Start.Column, ToRow: headerRowIndex + groupedRowData.Count(), ToCol: dataFields.Count()];
table = worksheet.Tables.Add(subRowDataRange, Name: null); // Auto generate Excel table name
table.TableStyle = TableStyles.Light13;
}
foreach (var field in dataFields)
{
// Headers
if (strategy == Strategy.CreateTableFirst)
{
table.Columns[dataFields.IndexOf(field)].Name = field.Name;
}
else
{
worksheet.Cells[headerRowIndex, columnIndex].Value = field.Name;
}
// Help Text
if (field.HelpText.HasValue())
{
worksheet.Cells[headerRowIndex, columnIndex].AddComment(field.HelpText, Author: "System");
}
int dataRowIndex = headerRowIndex + 1; // First row in the datatable
if (groupedRowData.None())
{
worksheet.Cells[dataRowIndex, columnIndex].Set(field, owner: owner, rowIndex: null, addValidation: true);
}
// Add SubRows
foreach (var rowData in groupedRowData)
{
worksheet.Cells[dataRowIndex, columnIndex].Set(field, owner: owner, rowIndex: rowData.Key, addValidation: true);
dataRowIndex++;
}
columnIndex++;
}
if (strategy == Strategy.CreateTableLast)
{
subRowDataRange = worksheet.Cells[headerRowIndex, worksheet.Dimension.Start.Column, ToRow: worksheet.Dimension.End.Row + 1, ToCol: dataFields.Count()];
table = worksheet.Tables.Add(subRowDataRange, Name: null);
table.TableStyle = TableStyles.Light13;
}
}
This is the output table in excel after the code:
The funny thing is, the cell validation is copied down to the next row fine if I create the table manually and have the first row set with the data, then drag down to make a new row and it copies over fine. I'm not sure how I am going to be able to export multiple rows of data and be assured that when a user inserts a new row, validation is copied down.
I downloaded the Microsoft XML SDK to compare the excel table with 1 row (which I am then able to drag down to create a second row with copied formula) and the original downloaded excel file with many rows in the excel table.
The results are almost identical with regards to the excel table in XML output.
Also nothing seems out of place after deleting the rows and saving the file for comparison.
Any EPPlus gurus have an idea?
Update: 30/04/2015. Client understands the issue and accepts it for what it is. No solution has been found.
I'm not familiar with EPPlus, but I've had this issue in VBA before and was able to force the table to fill by using VBA script that looks something like this:
LastRow = Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Range(Cells(TopRowOfTable,ColumnOfTableRow1),Cells(LastRow,ColumnOfTableRow1).Filldown
Basically just finding the last row, then using the filldown command to force the field to fill.

How to copy format of one row to another row in Excel with c#

I am inserting data to Excel using C#. Whenever I add a new row to Excel using C# I want the same format as above row i.e, color, font and background color everything by programmatically.
It's an OLEDB insert.
Post insert, I want to apply the format of first row to the second row. With format painter from UI it's a straightforward job, I can't find a way to do the same with C#.
1) First you Need to get the Range you want to copy for e.g. RngToCopy
2) Then Set the Range where you want to insert.
3) use the below mentioned code snippet.
Range RngToCopy = ws.get_Range(StartCell, EndCell).EntireRow;
Range RngToInsert = ws.get_Range(StartCell, Type.Missing).EntireRow;
oRngToInsert.Insert(Microsoft.Office.Interop.Excel.XlInsertShiftDirection.xlShiftDown, oRngToCopy.Copy(Type.Missing));
//ws is the worksheet object, set StartCell and EndCell as per your requirement

Datatable not returning values from Excel (values from formulas)

A project I am working on requires values from a C# application to update 6 values in an Excel sheet. This excel sheet then populates a named range of items and prices, driven by a number of VLOOKUP formulas and worksheets within the same Excel File.
The two columns in the range are named 'Item' & 'Price'.
'Item' is a column of items concatenated from formulas on a spreadsheet, and these appear fine in the DataTable. However, none of the values in the 'Price' column (all number values from VLOOKUP formulas) appear in the DataTable.
When loading the DataTable in C#, I can see that the two columns' types of string and double are fine. If I replace one of the formula derived values with a hard-coded value the DataTable is fine, so I guess the DataTable itself is working as expected.
If I open the Excel file after the values are passed into it, I can see that the input values went in fine and the formulas have calculated correctly.
So, the only thing I can think is causing the problem is that the formulas are not being recalculated after the values are passed into it.
Is there a way to open an excel file in C# and get it to recalculate all the formulas?
Never mind,
Replaced the OLEDB update with an Excel Interop, opening the Excel file with the Interop allows the formulas to recalculate.

Categories