C# EPPlus multiple cell formatting not applying - c#

I am using EPPlus to generate excel file from data table. i have only two rows. i am applying % formatting on first row and $ formatting on second row but my two row has getting same % formatting for first two row which is wrong. i am not being able to capture the reason why this is happening. why second formatting not being applied on second row which is $ formatting.
See this line where i use range to apply formatting.
ws.Cells["C0:P0"].Style.Numberformat.Format = "#,###,##0.0%;(#,###,##0.0%)";
ws.Cells["C1:P1"].Style.Numberformat.Format = "$##,##0.0;($##,##0.0)";
in the above code i mention cell range with formatting but my two row getting only first formatting and second formatting not consider...not clear why this is happening?
Sample Code
using (OfficeOpenXml.ExcelPackage obj = new OfficeOpenXml.ExcelPackage(FileLoc))
{
// creating work sheet object
OfficeOpenXml.ExcelWorksheet ws = obj.Workbook.Worksheets.Add("Vertical");
// freezing work sheet columns and rows
ws.View.FreezePanes(2, 3);
// exporting data to excel
ws.Cells["A1"].LoadFromDataTable(selected, true);
// setting calumns as autofit
ws.Cells[ws.Dimension.Address].AutoFitColumns();
//fixing height of column
ws.Row(1).Height = 16;
ws.Row(1).Style.Fill.PatternType = ExcelFillStyle.Solid;
ws.Row(1).Style.Fill.BackgroundColor.SetColor(Color.LightGray);
obj.Save();
ws.Cells["C0:P0"].Style.Numberformat.Format = "#,###,##0.0%;(#,###,##0.0%)";
ws.Cells["C1:P1"].Style.Numberformat.Format = "$##,##0.0;($##,##0.0)";
}
screen shot of excel data. see first two line in picture and definitely understand #,###,##0.0%;(#,###,##0.0%) this format is applying on first two row but in my code i have given different format for second records.
please help me to find the wrong things in my code. thanks

Well, there are a couple of errors. First, you're saving before setting the formatting, so it's not being applied.
Second, Excel addresses are base 1, it doesn't exist "C0" and "P0". Also note that in the first row is the columns titles, so you probably want rows 2 and 3. Try the following:
ws.Cells["C2:P2"].Style.Numberformat.Format = "#,###,##0.0%;(#,###,##0.0%)";
ws.Cells["C3:P3"].Style.Numberformat.Format = "$##,##0.0;($##,##0.0)";
obj.Save();

Related

How to determine header row while using ClosedXML

i have a small winforms application im working on and using ClosedXML to handle our excel files. Im trying to build the read logic in a way that no matter what row the headers are on, i can find that row and work with the data below that. Because our reports come from our enterprise reporting system, the files are not always the same in where they start with the data because the exports from our system appends the report filters and selections to the top x rows then below that it starts the data dump. So right now that only way i can get it to work is if i manually remove all those rows at the top and make the header row the first row.
Im looking for some assistance in how i can find the "header" row based on column names or any other method. I have already looked thru their wiki https://github.com/ClosedXML/ClosedXML/wiki but that only has mention of working with printing headers and footers..
Here is where i believe i need to focus my work, but unclear where to start:
// Look for the first row used
var firstRowUsed = ws.FirstRowUsed(); //{'Precision Calculator D'!A1:XFD1}
//var firstRowUsed = "'Precision Calculator D'!A9:XFD9";
// Narrow down the row so that it only includes the used part
var udasRow = firstRowUsed.RowUsed(); //{'Precision Calculator D'!A10:A10}
//var udasRow = "'Precision Calculator D'!A10:A10}";
// Move to the next row (it now has the titles)
udasRow = udasRow.RowBelow();
There are reports ive tried that have the header starting on row 5 and others that start on row 7 and so on, so there is no actual row that they will alays be on, so need to find a way to determine it automatically. is there anyway to determine the row that the column names are in? The columns will always be in the same order, so those i have determined.
So ran across this in a mention of closedXML and it def may help get me where i need to be, but unclear how to implement
var foundMonth = ws.Search("Month", System.Globalization.CompareOptions.OrdinalIgnoreCase);
Since it returns a IEnumerable there is a chance that there may be more than one cell with the value "Month" and in my file that im testing with, there is 2 rows that contain the word and not sure how i can determine in this case that i want the last cell it found if there are multiple.
Addressed the concern about the multiple cells returned, and can now determine which row the headers are on with the following:
var foundMonth = ws.Search("Month", System.Globalization.CompareOptions.OrdinalIgnoreCase);
var monthRow = foundMonth.Last().Address.ToString();
Still unclear how to implement this into the original code post above, so that the firstRowUsed is reflected correctly in this case would be A11:XFD11
After exhausting search of ClosedXML and reading thru a number of other questions, i was able to find a solution. Below is the code that will help set the used range based on my current data structure within the file..
var foundMonth = ws.Search("Month", System.Globalization.CompareOptions.OrdinalIgnoreCase);
var monthRow = foundMonth.Last().Address; // A11
var lastcell = ws.LastCellUsed().Address; // BC3950
var rangeUsed = ws.Range(monthRow, lastcell);
Since i have no idea where my header row will be from file to file, im searching for my column header name in column A, since all the usable data is mostly numbers i can safely assume that in column A, the last found instance of the word "Month" is my header row.
With that and the last cell used i am able to determine my data range as seen above. Although i still need to figure out how to replace my firstRowUsed logic to work the same way, this is a step closer to a final solution. Ill post back my findings on that one before i mark this question answered.
var firstRowUsed = ws.Range(monthRow, lastcell).FirstRowUsed();
This line provides you the same as this line below
var firstRowUsed = ws.FirstRowUsed();
I tried this logic with 3 different files, each one having more and less data and also having the header row on different rows. and works like a charm

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.

C# removing and re-adding rows to excel sheet

Simple question.
I have an excel sheet that I want to use as a database. I use linq-to-excel and it works wonderfully except it only works if the header row is the first row in the sheet and the spreadhseets I need to run on have other (important to the owners) data in the first 7 rows with the header row appearing in the 8th row.
What's the best way I can cut out these first rows through C# temporarily, so I can run my program and then re-insert them back in place after I've changed whatever records/columns/etc I needed to?
You can use LinqToExcel's WorksheetRange() method to select the specific range of cell's you want to select. This also allows you to use the first row of the range as a header row.
Here's a code example:
var excel = new ExcelQueryFactory("excelFileName");
var indianaCompanies = from c in excel.WorksheetRange<Company>("B3", "G10")
where c.State == "IN"
select c;
And here's the documentation

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

Categories