Formatting cells based on initial format [Very slow!] - c#

I want to create an application which exports Excel files into HTML files using the Excel interop dll. One catch is that I want to make sure the .HTML file preview looks as close to reality as if it was being viewed in Excel.
When normally exporting Excel files as HTML files no borders / 'grid lines' are shown. To fix this issue you must add your own borders.
However when Excel renders coloured cells, grid lines are not shown.
I wrote the below C# code to add borders to cells that have "no fill":
using Microsoft.Office.Interop.Excel;
namespace CS_HelloExcel
{
class Program
{
static void Main()
{
//Create excel application:
Application application = new Application();
//Define filename "C:\Users\sancarn\Documents\"
String filename = "C:\\Users\\sancarn\\Documents\\myFormatedXL.xlsx";
//Open workbook as read only, don't update links
Workbook workbook = application.Workbooks.Open(filename, false, true);
foreach (Worksheet sheet in workbook.Sheets)
{
Range ur = sheet.UsedRange;
ur = ur.Resize[ur.Rows.Count + 1, ur.Columns.Count + 1];
//Create individual cells where blank cells originally occur - Takes 0.2s
ur.Replace("", "'");
// Takes 3.4s
foreach(Range cell in ur.Cells)
{
if(cell.Interior.Pattern == -4142)
{
Borders b = cell.Borders;
b[XlBordersIndex.xlEdgeBottom].LineStyle= XlLineStyle.xlContinuous;
b[XlBordersIndex.xlEdgeBottom].ThemeColor = 3;
b[XlBordersIndex.xlEdgeBottom].Weight = 2;
b[XlBordersIndex.xlEdgeLeft].LineStyle= XlLineStyle.xlContinuous;
b[XlBordersIndex.xlEdgeLeft].ThemeColor = 3;
b[XlBordersIndex.xlEdgeLeft].Weight = 2;
b[XlBordersIndex.xlEdgeRight].LineStyle= XlLineStyle.xlContinuous;
b[XlBordersIndex.xlEdgeRight].ThemeColor = 3;
b[XlBordersIndex.xlEdgeRight].Weight = 2;
b[XlBordersIndex.xlEdgeTop].LineStyle= XlLineStyle.xlContinuous;
b[XlBordersIndex.xlEdgeTop].ThemeColor = 3;
b[XlBordersIndex.xlEdgeTop].Weight = 2;
}
}
}
//Save workbook as html file takes 0.5s
application.DisplayAlerts = false;
workbook.SaveAs("myFormatedXL.html",XlFileFormat.xlHtml);
//Get workbook path
String path = workbook.FullName;
//Close application
workbook.Close(false);
application.Quit();
//Free up memory
application = null;
}
}
}
However as shown in the comments the the loop over all the cells takes 3.5 seconds... I was wondering if anyone knows how I can speed up this task?

This option seems to have better results:
//Takes 0ms?
Range b=null;
foreach(Range cell in ur.Cells)
{
if(cell.Interior.Pattern == -4142)
{
b = b!=null ? application.Union(b, cell) : cell;
}
}
b.Borders[XlBordersIndex.xlEdgeBottom].LineStyle = XlLineStyle.xlContinuous;
b.Borders[XlBordersIndex.xlEdgeBottom].ThemeColor = 3;
b.Borders[XlBordersIndex.xlEdgeBottom].Weight = 2;
b.Borders[XlBordersIndex.xlEdgeTop].LineStyle = XlLineStyle.xlContinuous;
b.Borders[XlBordersIndex.xlEdgeTop].ThemeColor = 3;
b.Borders[XlBordersIndex.xlEdgeTop].Weight = 2;
b.Borders[XlBordersIndex.xlEdgeLeft].LineStyle = XlLineStyle.xlContinuous;
b.Borders[XlBordersIndex.xlEdgeLeft].ThemeColor = 3;
b.Borders[XlBordersIndex.xlEdgeLeft].Weight = 2;
b.Borders[XlBordersIndex.xlEdgeRight].LineStyle = XlLineStyle.xlContinuous;
b.Borders[XlBordersIndex.xlEdgeRight].ThemeColor = 3;
b.Borders[XlBordersIndex.xlEdgeRight].Weight = 2;
b.Borders[XlBordersIndex.xlInsideHorizontal].LineStyle = XlLineStyle.xlContinuous;
b.Borders[XlBordersIndex.xlInsideHorizontal].ThemeColor = 3;
b.Borders[XlBordersIndex.xlInsideHorizontal].Weight = 2;
b.Borders[XlBordersIndex.xlInsideVertical].LineStyle = XlLineStyle.xlContinuous;
b.Borders[XlBordersIndex.xlInsideVertical].ThemeColor = 3;
b.Borders[XlBordersIndex.xlInsideVertical].Weight = 2;
First we make the range that needs to be formatted, and then afterwards we apply the formatting. However for some reason the formatting that comes as a result of this is... odd (in the resulting html file).
I might want to do this in CSS instead.

Related

Row height and background color the same as sheet's one leads to default style in ClosedXML

I was following this example and noticed that when a row has height changed and background color the same as for its sheet then background color disappears from the row (or it changes to the Default one).
It seems to be that style is completely lost for such rows:
Code to reproduce problem:
Different colors for sheet (Green) and row (Red) - CORRECT
public void Main()
{
var document = new XLWorkbook();
var ws = document.Worksheets.Add("Row Settings");
ws.Style.Fill.BackgroundColor = XLColor.Green;
var row1 = ws.Row(2);
row1.Style.Fill.BackgroundColor = XLColor.Red;
row1.Height = 30;
var row2 = ws.Row(4);
row2.Style.Fill.BackgroundColor = XLColor.DarkOrange;
row2.Height = 3;
document.SaveAs("Correct-Diff.xlsx");
}
The same colors for sheet (Red) and row (Red) WITHOUT height adjustment - CORRECT
public void Main()
{
var document = new XLWorkbook();
var ws = document.Worksheets.Add("Row Settings");
ws.Style.Fill.BackgroundColor = XLColor.Red;
var row1 = ws.Row(2);
row1.Style.Fill.BackgroundColor = XLColor.Red;
var row2 = ws.Row(4);
row2.Style.Fill.BackgroundColor = XLColor.DarkOrange;
row2.Height = 3;
document.SaveAs("Correct-NoHeight.xlsx");
}
The same colors for sheet (Red) and row (Red) WITH height adjustment - INCORRECT
public void Main()
{
var document = new XLWorkbook();
var ws = document.Worksheets.Add("Row Settings");
ws.Style.Fill.BackgroundColor = XLColor.Red;
var row1 = ws.Row(2);
row1.Style.Fill.BackgroundColor = XLColor.Red;
row1.Height = 30;
var row2 = ws.Row(4);
row2.Style.Fill.BackgroundColor = XLColor.DarkOrange;
row2.Height = 3;
document.SaveAs("Incorrect.xlsx");
}
Is there a way to resize a row while keeping its color the same as sheet one, basically achieving the correct for of 3rd example?
P.S. It's a copy of the issue I opened just in case if somebody else experienced and fixed the issue
As was expected it's a bug that should be closed by the fix

Add text to Excel sheet before data conversion

I want to add some basic information regarding the DataGridView before my DGV gets converted to Excel. This is an example from the internet sample output, currently I can upload all the data to Excel, but I'm not sure how to hardcode the title (row1 from pic) and some basic information (row 2 from pic) before the data gets converted.
If anything is unclear let me know.
//Create headers
for (int i = 0; i < dv.Columns.Count; i++)
{
Excel.Range CellHeadersRange = ws.get_Range(GetExcelColumnName(i + 1) + rowstartindex.ToString(), GetExcelColumnName(i + 1) + rowstartindex.ToString());
//Setting the borders
CellHeadersRange.BorderAround2(LineStyle.Thin, Excel.XlBorderWeight.xlThin, Excel.XlColorIndex.xlColorIndexNone,
Color.FromArgb(255, 0, 0), Type.Missing);
//Aligning headers to the middle
CellHeadersRange.HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter;
//Updating header value to Excel
CellHeadersRange.Value = dv.Columns[i].HeaderText;
CellHeadersRange.Font.Bold = true;
double widthDGV = (dv.Columns[i].Width) / 10;
Math.Ceiling(widthDGV);
CellHeadersRange.ColumnWidth = widthDGV * 2;
}
//Write data
for (int i = 0; i < dv.Rows.Count - 1; i++)
{
for (int j = 0; j < dv.Columns.Count; j++)
{
Excel.Range CellDataRange = ws.get_Range(GetExcelColumnName(j + 1) + (i + rowstartindex + 1).ToString());
CellDataRange.WrapText = true;
//Setting borders for all cells
CellDataRange.BorderAround2(LineStyle.Thin, Excel.XlBorderWeight.xlThin, Excel.XlColorIndex.xlColorIndexNone, Color.FromArgb(255, 0, 0), Type.Missing);
CellDataRange.Value = dv[j, i].Value;
//Verify that backgroundcolor of datagrid is not RGB(0,0,0,0) and in that case apply datagridviewcell color to excel range
if (!dv.Rows[i].Cells[j].Style.BackColor.IsEmpty)
CellDataRange.Interior.Color = dv.Rows[i].Cells[j].Style.BackColor;
//Verify that font style exist before checking for bold and in that case apply datagridviewcell font.bold property to excel range
if (dv.Rows[i].Cells[j].Style.Font != null)
CellDataRange.Font.Bold = dv.Rows[i].Cells[j].Style.Font.Bold;
}
}
wb = null;
ws = null;
xlApp = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Edit: Maybe my question is all over places due to that I'm getting no response so I was wondering how can I add data to the first row of an existing excel sheet? Lets say using my code i first generate my excel sheet than I would like to insert couple new rows in the beginning of the sheet. How can I achieve that??
Solution:
//Writitng the title
Excel.Range line = (Excel.Range)ws.Rows[1];
line.Insert();
Excel.Range rng = ws.get_Range("B1");
rng.RowHeight = 35;
rng.Font.Bold = true;
rng.Value = "Assumptions";
rng.HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter;
rng.Font.Size = 14;
rng.Interior.Color = Color.LightBlue;
rng = ws.get_Range("A1");
rng.Interior.Color = Color.LightBlue;
You can manipulate where you want to insert row by changing this ws.Rows[x];

Append columns before Sheetdata

I need to add columns with specific widths to Worksheet while creating new Excel file. According to everywhere I looked this has to be done before Sheetdata (one link here). However I tried numerious things but can't get It working. My code for creating Excel file is from official site (link here - It's meant for ASP.NET but works fine for me).
Here is my code and last attempt to get It working:
public void Export_Datagridview(DataGridView dgv, string filename)
{
using (var workbook = SpreadsheetDocument.Create(filename, SpreadsheetDocumentType.Workbook))
{
var workbookPart = workbook.AddWorkbookPart();
workbook.WorkbookPart.Workbook = new Workbook();
workbook.WorkbookPart.Workbook.Sheets = new Sheets();
var sheetPart = workbook.WorkbookPart.AddNewPart<WorksheetPart>();
var sheetData = new SheetData();
//this part is giving me "object reference" error
sheetPart.Worksheet.InsertBefore(AutoFit_Columns(dgv, sheetPart.Worksheet), sheetData);
...//and so on...
And code for adding columns :
private Columns AutoFit_Columns(DataGridView dgv, Worksheet worksheet)
{
Columns cols = new Columns();
for (int col = 0; col < dgv.ColumnCount; colc++)
{
double max_width = 14.5; //something like default width in Excel
for (int row = 0; row < dgv.RowCount; row++)
{
double cell_width = Text_width(dgv.Rows[row].Cells[col].Value.ToString(), new System.Drawing.Font("Arial", 12.0F));
if (cell_width > max_width)
{
max_width = cell_width;
}
if (row == dgv.RowCount - 1) //last iteration - here we allready have max width within column
{
Column c = new Column() { Min = Convert.ToUInt32(col), Max = Convert.ToUInt32(col), Width = max_width, CustomWidth = true };
cols.Append(c);
worksheet.Append(cols);
}
}
}
return cols;
}
As you see, this is my attempt to Autofit columns based on data that get's exported from Datagridview. But before I can test other code I need to add columns properly. Any help kindly appreciated !
Figured It out, wasn't so easy to solve and identify all problems. Firstly I needed to change upper code to this:
public void Export_Datagridview(DataGridView dgv, string filename)
{
using (var workbook = SpreadsheetDocument.Create(filename, SpreadsheetDocumentType.Workbook))
{
var workbookPart = workbook.AddWorkbookPart();
workbook.WorkbookPart.Workbook = new Workbook();
workbook.WorkbookPart.Workbook.Sheets = new Sheets();
var sheetPart = workbook.WorkbookPart.AddNewPart<WorksheetPart>();
var sheetData = new SheetData();
//this part is new - I had to change method for autofit too
sheetPart.Worksheet = new Worksheet();
sheetPart.Worksheet.Append(AutoFit_Columns(dgv));
sheetPart.Worksheet.Append(sheetData);
...//and so on...
and then change Autofit method. It was still causing errors because loop started from 0, and there is no Column with index of 0. Changed It to 1:
for (int col = 1; col < dgv.ColumnCount; colc++)
{
Excel now opens with column widths set, though code for autofit needed adjustments. If anyone interested, here is my complete solution with autofit included.

Add Text to Excel Using EPplus

I have a main directory which contains many sub directories. In each sub directory there will be images. I have managed to export images from each subdirectory into each excel spreadsheet in a excel workbook. For example, if I have 10 sub directories, there will be 1 excel workbook with 10 excel spreadsheets, in each spreadsheet there will be each sub directory's images.
What I want to accomplish now is if there is no image in any sub directory, the sub directory exported to an excel spreadsheet will be blank. I want to add "No image found" to that blank excel spreadsheet as text.
This is what I have tried:
foreach (string subdir in filesindirectory)
{
string[] splitter = subdir.Split('\\');
string folderName = splitter[splitter.Length - 1];
ExcelWorksheet ws = package.Workbook.Worksheets.Add("Worksheet-" + folderName); //create new worksheet
ImageCount = 0;
if (Directory.GetFiles(subdir).Length == 0)
{
ws.Cells["J9:L10"].Merge = true;
ws.Cells["J9:L10"].Style.VerticalAlignment = ExcelVerticalAlignment.Top;
ws.Cells["J9:L10"].Value = "No chart to display";
ws.Cells["J9:L10"].Style.Font.Size = 16;
}
foreach (string img in Directory.GetFiles(subdir))
{
ImageCount++;
System.Drawing.Image Image1 = System.Drawing.Image.FromFile(img);
var AddImage = ws.Drawings.AddPicture("Image" + ImageCount.ToString(), Image1 );
Image1 .Dispose();
// Row, RowoffsetPixel, Column, ColumnOffSetPixel
if (ImageCount > 1)
{
AddImage .SetPosition(ImageFinalPosition, 0, 2, 0);
AddImage .SetSize(770, 450);
ImageFinalPosition += (ImagePosition + 1); // Add 1 to have 1 row of empty row
}
else
{
AddImage.SetPosition(ImageCount, 0, 2, 0);
AddImage.SetSize(770, 450);
ImageFinalPosition = (ImageCount + ImagePosition) + 1; // Add 1 to have 1 row of empty row
}
The problem I faced now is when I add the text to specific merged cells, if I view the spreadsheet with different screen size, smaller screen size will display text at centre of spreadsheet while larger screen size will display the text on the left of the spreadsheet.
This is a sample of what I see:
Smaller screensize(laptop):
Larger screensize(desktop):
This is the output I want to achieve(all screensize):
Please help me on this, thanks!
You must understand how foreach loop works..
If your count is 0 your code in foreach doesn't run at all
You should put your checking before the foreach loop to write inside the worksheet
Put your code before the foreach loop as per below
if (Directory.GetFiles(subdir).Length == 0) //if no image in that sub directory
{
ws.Cells["A1:A3"].Merge = true;
ws.Cells["A1:A3"].Style.VerticalAlignment = ExcelVerticalAlignment.Top;
ws.Cells["A1:A3"].Style.Border.Top.Style = ExcelBorderStyle.Thin;
ws.Cells["A1:A3"].Style.Border.Left.Style = ExcelBorderStyle.Thin;
ws.Cells["A1:A3"].Style.Border.Right.Style = ExcelBorderStyle.Thin;
ws.Cells["A1:A3"].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
ws.Cells["A1:A3"].Style.Fill.PatternType = ExcelFillStyle.Solid;
ws.Cells["A1:A3"].Style.Fill.BackgroundColor.SetColor(System.Drawing.ColorTranslator.FromHtml("#f0f3f5"));
ws.Cells["A1:A3"].Value = "content not found";
}
foreach (string img in Directory.GetFiles(subdir))
{
// Your else code
}

Highlighting the data with different colors in Excel Using C# in Windows Applications

I am working with a Windows application. I need to work out how to highlight the data with different colors & styles in Excel. I am using C# to export the data to excel.
This is the code I am using to export a DataTable into Excel,
private void btnExportexcel_Click(object sender, EventArgs e)
{
oxl = new Excel.Application();
oxl.Visible = true;
oxl.DisplayAlerts = false;
wbook = oxl.Workbooks.Add(Missing.Value);
wsheet = (Excel.Worksheet)wbook.ActiveSheet;
wsheet.Name = "Customers";
DataTable dt = clsobj.convert_datagrid_orderlist_to_datatable(dvgorderlist);
int rowCount = 1;
foreach (DataRow dr in dt.Rows)
{
rowCount += 1;
for (int i = 1; i < dt.Columns.Count + 1; i++)
{
// Add the header the first time through
if (rowCount == 2)
{
wsheet.Cells[1, i] = dt.Columns[i - 1].ColumnName;
}
wsheet.Cells[rowCount, i] = dr[i - 1].ToString();
}
}
range = wsheet.get_Range(wsheet.Cells[1, 1],
wsheet.Cells[rowCount, dt.Columns.Count]);
range.EntireColumn.AutoFit();
}
wsheet = null;
range = null;
}
You need to get the 'Interior' object of the cell or range and set the colour on it.
Range cellRange = (Range)wsheet.Cells[rowCount, i];
cellRange.Interior.Color = 255;
Excel colours are an integer sequence, so you have to calculate the value for the colour your want. You might find this method helpful:
public static int ConvertColour(Color colour)
{
int r = colour.R;
int g = colour.G * 256;
int b = colour.B * 65536;
return r + g + b;
}
Then you can just do this:
cellRange.Interior.Color = ConvertColour(Color.Green);
You can set the style of the text using the .font property:
cellRange.Font.Size = "20";
cellRange.Font.Bold = true;
There are other properties like Color, Italic and Underline which you can use to get the style you need.
I realize I am about a decade late, but as a simpler alternative to Simon's custom ConvertColour method, you can also use the ColorTranslator.ToOle method which is also in the System.Drawing namespace.
For example, the one-liner to do yellow highlighting of a defined range would be:
range.Interior.Color = ColorTranslator.ToOle(Color.Yellow);

Categories

Resources