How to set the background of several cells within a row (or of a whole row) in OpenXml?
Having read several articles:
Coloring cells in excel sheet using openXML in C#
Advanced styling in Excel Open XML
I still cannot make it work.
My task is actually at first glance seems to be somewhat easier and a little bit different from what is written in those articles. The mentioned tutorials predominantly show how to create a new document and style it. While I need to change the styling of the existing one.
That is, I have an existing xlsx document (a report template). I populate the report with the necessary values (managed to do it thanks to SO open xml excel read cell value and MSDN Working with sheets (Open XML SDK)). But next I need to mark several rows with, say, red background.
I am neither sure whether to use CellStyle nor if I should use CellFormat or something else...This is what I have got up to now:
SpreadsheetDocument doc = SpreadsheetDocument.Open("ole.xlsx", true);
Sheet sheet = (Sheet)doc.WorkbookPart
.Workbook
.Sheets
.FirstOrDefault();
WorksheetPart worksheetPart = (WorksheetPart)doc.WorkbookPart
.GetPartById(sheet.Id);
Worksheet worksheet = worksheetPart.Worksheet;
CellStyle cs = new CellStyle();
cs.Name = StringValue.FromString("Normal");
cs.FormatId = 0;
cs.BuiltinId = 0;
//where are the style values?
WorkbookStylesPart wbsp = doc.WorkbookPart
.GetPartsOfType<WorkbookStylesPart>()
.FirstOrDefault();
wbsp.Stylesheet.CellStyles.Append(cs);
wbsp.Stylesheet.Save();
Cell cell = GetCell(worksheet, "A", 20);
cell.StyleIndex = 1U;//get the new cellstyle index somehow
doc.Close();
Actually I would greatly appreciate a more light-weight and easy example of how to style, say, cell A20 or range from A20 to J20. Or probably a link to some more consecutive tutorial.
In the end I changed my mind to use cell background and used fonts. Thanks to answer by foson in SO Creating Excel document with OpenXml sdk 2.0 I managed to add a new Font and a new CellFormat, having preserved the original cell's formatting (i.e. having changed the font color only):
SpreadsheetDocument doc = SpreadsheetDocument.Open("1.xlsx", true);
Sheet sheet = (Sheet)doc.WorkbookPart.Workbook.Sheets.FirstOrDefault();
WorksheetPart worksheetPart = (WorksheetPart)doc.WorkbookPart
.GetPartById(sheet.Id);
Worksheet worksheet = worksheetPart.Worksheet;
WorkbookStylesPart styles = doc.WorkbookPart.WorkbookStylesPart;
Stylesheet stylesheet = styles.Stylesheet;
CellFormats cellformats = stylesheet.CellFormats;
Fonts fonts = stylesheet.Fonts;
UInt32 fontIndex = fonts.Count;
UInt32 formatIndex = cellformats.Count;
Cell cell = GetCell(worksheet, "A", 19);
cell.CellValue = new CellValue(DateTime.Now.ToLongTimeString());
cell.DataType = new EnumValue<CellValues>(CellValues.String);
CellFormat f = (CellFormat)cellformats.ElementAt((int)cell.StyleIndex.Value);
var font = (Font)fonts.ElementAt((int)f.FontId.Value);
var newfont = (Font)font.Clone();
newfont.Color = new Color() { Rgb = new HexBinaryValue("ff0000") };
fonts.Append(newfont);
CellFormat newformat = (CellFormat)f.Clone();
newformat.FontId = fontIndex;
cellformats.Append(newformat);
stylesheet.Save();
cell.StyleIndex = formatIndex;
doc.Close();
You have 3 options:
Use MS lib ExcelDataReader which requires your server installing Office and usually does not work if your program is running in IIS.
Use closed source libs.
Use OpenXML.
Try my code using pure OpenXML:
https://stackoverflow.com/a/59806422/6782249
cSimple solution:
Try this: (it works with the nuget package ClosedXML v 0.95.4)
using ClosedXML.Excel;
XLWorkbook wb = new XLWorkbook();
IXLWorksheet ws = wb.Worksheets.Add("Test Background Color");
ws.Cell("A1").Style.Fill.BackgroundColor = XLColor.LightBlue;
ws.Cell("A1").Value = "This cell should have light blue background";
wb.SaveAs(#"c:\Test\test.xlsx");
Related
I want read the styles of this worksheet from Excel via ClosedXML. So on column "E" there is a Fill -> BackgroundColor (blue) and on row "15" there is a Fill -> BackgroundColor (purple). How can I read via ClosedXML which property is now the overlapping one (or which property was set last) so I can set the styles for "E15" correctly?
I was looking for some kind of attribute which indicates that the row "15" was set last and is now the "top layer" but I had no luck so far.
Has someone experienced a similar problem?
string fileName = "d:\\test.xlsx";
using FileStream fs = File.OpenRead(fileName);
using XLWorkbook workbook = new XLWorkbook(fs);
IXLWorksheet worksheet = workbook.Worksheets.Worksheet("Test");
IXLCell cell = worksheet.Cell(5, "E");
Console.WriteLine($"5E value:{cell.Value} color:{cell.Style.Fill.BackgroundColor}");
cell = worksheet.Cell(6, "E");
Console.WriteLine($"6E value:{cell.Value} color:{cell.Style.Fill.BackgroundColor}");
Poperty BackgroundColor contains actual color
screenshot of my test excel
Also you can try to get all styles of worksheet.
((ClosedXML.Excel.XLWorksheet)worksheet).Styles
But XLWorksheet internal class and you get access to it only using reflection and this is not good practice.
I am using SoftArtisans OfficeWriter tool to create an excel file.
By Using DataImportProperties.UseColumnNames=true the column headers are populated with the class property names but I want to give custom column headers.
Any suggestions?
Donot use DataImportProperties.UseColumnNames=true.Instead use cell specific formatting since it is header so for every cell row/column number is known.ex.
Style headerStyle = wb.CreateStyle();
headerStyle.Font.Size = 10;
headerStyle.Font.Bold = true;
ws.Cells[1, 0].Value = "Name";
ws.Cells[1, 0].ApplyStyle(headerStyle);
You can also merge and group columns as :
ws[0, 0].Value = "Information";
Palette pal = wb.Palette;
Color group1Color = pal.GetClosestColor(255, 244, 205);
headerStyle.BackgroundColor = group1Color;
headerStyle.Font.Bold = true;
ws[0, 0].ApplyStyle(headerStyle);
ws.CreateArea(0, 0, 1, 13).MergeCells();
ws.GroupColumns(0, 12, true);
//True/false keeps the group collapsed/uncollpasedwhen user opens the workbook.
In my WinApp I export data to a specific tab of an Excel spreadsheet, in which there are macros (file extension .xlxm).
In this workbook, the data is always inserted from the same cell when it is empty, but it can happen that has already had the previous entries, so you have to retrieve the first available blank cell on the new line after the one that has already had the data inserted. The sequence of entries in the cells is similar to the following: in cells A1: A3, and then such as A10: A15, dropping the cells A4 to A9 because they are cells with formulas. I would to add that I must to control every cell of workbook for to fill in data from winApp. I hope to be able to explain the scenario.
You could use a library such as EPPlus from NuGet to achieve this. Something like this would do the trick.
static void Main(string[] args)
{
List<string> ExampleData = new List<string> { "my", "intestesting", "data" };
using (ExcelPackage package = new ExcelPackage(new FileInfo(#"C:\Temp\example.xlsm")))
{
ExcelWorksheet ws = package.Workbook.Worksheets["MySheet"];
int lastRowIndex = ws.Dimension.End.Row;
int idx = lastRowIndex + 1;
foreach (var datum in ExampleData)
{
ws.Cells[idx, 1].Value = datum;
idx++;
}
package.Save();
}
}
I have a formula cell C4 that needs to recalculate after I enter a value in another cell C2. but the C4 keeps getting cached and keeps returning old cached value.
I have asked this question multiple times on SO but I am not getting any help. I am trying every thing that I can. Here is what I found on msdn site.
With the methods from the previous code listing in place, generating
the report is now a process of getting the portfolio data and
repeatedly calling UpdateValue to create the report. Indeed, if you
add the necessary code to do this, things seem to work fine except for
one problem - any cell that contains a formula that refers to a cell
whose value was changed via Open XML manipulation does not show the
correct result. This is because Excel caches the result of a formula
within the cell. Because Excel thinks it has the correct value cached,
it does not recalculate the cell. Even if you have auto calculation
turned on or if you press F9 to force a manual recalculation, Excel
does not recalculate the cell. The solution to this is to remove the
cached value from these cells so that Excel recalculates the value as
soon as the file is opened in Excel. Add the RemoveCellValue method
shown in the following example to the PortfolioReport class to provide
this functionality.
Based on above MSDN explanation. I have tried putting the removing the code before I update the cell. After I update the cell. Before I read the formula cell, after I read the formula cell but I keep getting the following error after I read the formula cell.
System.NullReferenceException: Object reference not set to an instance
of an object.
Here is my code...
string filename = Server.MapPath("/") + "MyExcelData.xlsx";
using (SpreadsheetDocument document = SpreadsheetDocument.Open(filename, true))
{
Sheet sheet = document.WorkbookPart.Workbook.Descendants<Sheet>().SingleOrDefault(s => s.Name == "myRange1");
if (sheet == null)
{
throw new ArgumentException(
String.Format("No sheet named {0} found in spreadsheet {1}", "myRange1", filename), "sheetName");
}
WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(sheet.Id);
Worksheet ws = worksheetPart.Worksheet; // ((WorksheetPart)(worksheetPart.GetPartById(sheet.Id))).Worksheet;
Cell cell = InsertCellInWorksheet(ws, "C4");
// If there is a cell value, remove it to force a recalculation
// on this cell.
if (cell.CellValue != null)
{
cell.CellValue.Remove();
}
// Save the worksheet.
ws.Save();
document.Close();
}
// getting 2 numbers in excel sheet, saving, and closing it.
using (SpreadsheetDocument document = SpreadsheetDocument.Open(filename, true))
{
Sheet sheet = document.WorkbookPart.Workbook.Descendants<Sheet>().SingleOrDefault(s => s.Name == "myRange1");
if (sheet == null)
{
throw new ArgumentException(
String.Format("No sheet named {0} found in spreadsheet {1}", "myRange1", filename), "sheetName");
}
WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(sheet.Id);
int rowIndex = int.Parse("C3".Substring(1));
Row row = worksheetPart.Worksheet.GetFirstChild<SheetData>().
Elements<Row>().FirstOrDefault(r => r.RowIndex == rowIndex);
Cell cell3 = row.Elements<Cell>().FirstOrDefault(c => "C3".Equals(c.CellReference.Value));
if (cell3 != null)
{
cell3.CellValue = new CellValue("16");
cell3.DataType = new DocumentFormat.OpenXml.EnumValue<CellValues>(CellValues.Number);
}
worksheetPart.Worksheet.Save();
document.Close();
}
// getting the result out of excel.
using (SpreadsheetDocument document = SpreadsheetDocument.Open(filename, false))
{
document.WorkbookPart.Workbook.CalculationProperties.ForceFullCalculation = true;
document.WorkbookPart.Workbook.CalculationProperties.FullCalculationOnLoad = true;
Sheet sheet = document.WorkbookPart.Workbook.Descendants<Sheet>().SingleOrDefault(s => s.Name == "myRange1");
if (sheet == null)
{
throw new ArgumentException(
String.Format("No sheet named {0} found in spreadsheet {1}", "myRange1", filename), "sheetName");
}
WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(sheet.Id);
int rowIndex = int.Parse("C4".Substring(1));
Row row = worksheetPart.Worksheet.GetFirstChild<SheetData>().
Elements<Row>().FirstOrDefault(r => r.RowIndex == rowIndex);
Cell cell = row.Elements<Cell>().FirstOrDefault(c => "C4".Equals(c.CellReference.Value));
d.Average = Convert.ToDouble(cell.CellValue.InnerText);
}
The problem seems to be that you are directly modifying an Excel data file without Excel being open. Since Excel can only track formula dependencies when its open it does not know that it needs to recalculate when you change data without Excel knowing that you have done so.
3 possible solutions are:
1) remove the calculation chain part from the file (not tested)
2) after making the changes to the file use interop/automation to open Excel and request a full calculation (or full calculation with dependency rebuild if you are also altering/creating formulas)
3) set the fullcalculationonload property to true : this should cause Excel to do a full calculation when it opens the file
I think u have deleted the cellValue of C4 ,, first u have to create the cellValue then u can perform any operation on it .
I'm working on this program that will read the data in excel file and put it into our database. The program is written in Visual Studio 2010 using C#, and I'm using the NPOI library.
In the past, I was able to read the spreadsheet row by row and cell by cell to get the data, but the new format of the excel file will not allow me to do this easily. (The excel is given by another user, so I can't really make big changes to it).
There are several "tables" in one sheet (using borders and headers for each column name), and I will need to get data mainly from the tables but sometimes outside the tables too.
I was wondering if I were to read the spreadsheet row by row (which is what I'm a bit for familiar with), is there a way I can tell that I have reached a table? Is there a way I can read the "format" of the cell?
What I mean is, for example, "this cell has borders around it so starting this row is a table." or "the text in this cell is bold, so this row is the header row for this new table."
In the past I was only able to read the "text" for the spreadsheet and not the format/style. I've been searching on the internet and I can only find how to set the style for output excel but not how to read the format from input.
Any help is appreciated, thanks!
It would be better to have the various tables in your source workbook defined as named ranges with known names. Then you can get the associated area like this -
using System.IO;
using System.Windows;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
// ...
using (var file = new FileStream(workbookLocation, FileMode.Open, FileAccess.Read))
{
var workbook = new XSSFWorkbook(file);
var nameInfo = workbook.GetName("TheTable");
var tableRange = nameInfo.RefersToFormula;
// Do stuff with the table
}
If you have no control over the source spreadsheet and cannot define the tables as named ranges, you can read the cell formats as you suggest. Here is an example of reading the TopBorder style -
using (var file = new FileStream(workbookLocation, FileMode.Open, FileAccess.Read))
{
var workbook = new XSSFWorkbook(file);
var sheet = workbook.GetSheetAt(0);
for (int rowNo = 0; rowNo <= sheet.LastRowNum; rowNo++)
{
var row = sheet.GetRow(rowNo);
if (row == null) // null is when the row only contains empty cells
continue;
for (int cellNo = 0; cellNo <= row.LastCellNum; cellNo++)
{
var cell = row.GetCell(cellNo);
if (cell == null) // null is when the cell is empty
continue;
var topBorderStyle = cell.CellStyle.BorderTop;
if (topBorderStyle != BorderStyle.None)
{
MessageBox.Show(string.Format("Cell row: {0} column: {1} has TopBorder: {2}", cell.Row.RowNum, cell.ColumnIndex, topBorderStyle));
}
}
}
}