Add text to Excel sheet before data conversion - c#

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];

Related

Formatting cells based on initial format [Very slow!]

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.

How do I get the number of rows used by the merged cells in Excel, using ClosedXml and C#

As an overview I am currently exporting data from my database and exporting it to an excel sheet.
I have formatted some of the cells (i.e Merged cells etc) in the excel sheet using ClosedXml, now I am little stuck with a small issue which I can get around as of now.
I want to fill the cells with a background color, but what is hindering is the merged cells. I am not able to color all the cells in the rows which are used by the merged cells.
The current output in the excel is somewhat like this:
The solutions I have tried fill the merged cells correctly, but the adjacent unmerged cells in the row arent all filled with the background color.
Could someone please give me an idea to go about this hurdle?
This is a sample POC code I have been working on,
private static void ToExcel(System.Data.DataTable dataTable, HttpResponseBase response, string fileName)
{
using (XLWorkbook wb = new XLWorkbook())
{
wb.CalculateMode = XLCalculateMode.Auto;
var ws = wb.Worksheets.Add(dataTable, "OKR Quater Report");
ws.Tables.FirstOrDefault().ShowAutoFilter = false;
var colRange = ws.Columns();
colRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
colRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
var headingCells = ws.Row(1).Cells();
headingCells.Style.Font.Bold = true;
headingCells.Style.Fill.SetBackgroundColor(XLColor.AirForceBlue);
headingCells.Style.Font.SetFontColor(XLColor.White);
ws.SheetView.FreezeRows(1);
ws.Column(3).Hide();
ws.Column(8).Hide();
for (int i = 2; i <= dataTable.Rows.Count + 1; i++)
{
for (int k = 1; k <= dataTable.Columns.Count - 4; k++)
{
if (ws.Column(k).IsHidden)
{
continue;
}
List<IXLCell> mergeRange = new List<IXLCell>();
int j = i;
while (j <= dataTable.Rows.Count + 1)
{
if (ws.Column(k).Cell(j).Value.ToString().Equals(ws.Column(k).Cell(j + 1).Value.ToString()) && ws.Column(1).Cell(j).Value.ToString().Equals(ws.Column(1).Cell(j + 1).Value.ToString())) //&& ws.Column(5).Cell(j).Value.ToString().Equals(ws.Column(5).Cell(j + 1).Value.ToString())
{
mergeRange.Add(ws.Column(k).Cell(j));
j++;
}
else
{
mergeRange.Add(ws.Column(k).Cell(j));
break;
}
}
if (mergeRange != null && mergeRange.Count > 0)
{
ws.Range(mergeRange.First(), mergeRange.Last()).Merge(false);
}
}
}
//Converting All string in the attachement column to Hyperlinks
for (int i = 2; i <= dataTable.Rows.Count + 1; i++)
{
if (!(ws.Column(10).Cell(i).Value.ToString().Equals(string.Empty)))
{
ws.Column(10).Cell(i).FormulaA1 = "=HYPERLINK" + ws.Column(10).Cell(i).Value.ToString();
}
}
//Writing datatable to Excel
MemoryStream stream = GetStream(wb);// The method is defined below
response.Clear();
response.Buffer = true;
response.AddHeader("content-disposition",
"attachment; filename=" + fileName + "_" + DateTime.Now.ToString() + ".xlsx;");
response.ContentType = "application/vnd.ms-excel";
response.BinaryWrite(stream.ToArray());
response.End();
}
}
You can use ws.MergedRanges to get a list of all the merged cells on the sheet. To find the merged range which includes a given cell, you can do something like this: ws.MergedRanges.First(r => r.Contains("B4")). Apply the styling to the entire range that you retrieve.

How can I prevent the left "gutter" (row number) column from printing (EPPlus)?

The "Print Preview" of a spreadsheet created with my EPPlus code shows the gutter/row number column (column 0, so to speak):
How can I programmatically prevent the gutter/row number ("crack?") column from printing?
My printing setup code is currently as follows:
private void ConfigureCustomerSheetForPrinting()
{
string columnName = GetExcelTextColumnName(customerWorksheet.Dimension.End.Column);
string printArea = string.Format("A1:{0}{1}", columnName, customerWorksheet.Dimension.End.Row);
customerWorksheet.PrinterSettings.PrintArea = customerWorksheet.Cells[printArea];
customerWorksheet.PrinterSettings.FitToPage = true;
customerWorksheet.PrinterSettings.Orientation = eOrientation.Landscape;
customerWorksheet.View.ZoomScale = 100;
customerWorksheet.PrinterSettings.FitToPage = true;
customerWorksheet.PrinterSettings.FitToHeight = 100;
customerWorksheet.PrinterSettings.Scale = 100;
customerWorksheet.PrinterSettings.LeftMargin = (decimal).5 / 2.54M;
customerWorksheet.PrinterSettings.RightMargin = (decimal).5 / 2.54M;
customerWorksheet.PrinterSettings.TopMargin = (decimal).5 / 2.54M;
customerWorksheet.PrinterSettings.BottomMargin = (decimal).5 / 2.54M;
customerWorksheet.PrinterSettings.HeaderMargin = (decimal).5 / 2.54M;
customerWorksheet.PrinterSettings.FooterMargin = (decimal).5 / 2.54M;
}
If I understand what you are asking for you want to show the Column headers ( say "Row 0" as "A", "B", "C", ...) but NOT show the Row headers (say "Column 0" as "1", "2" 3", etc.). If so, I have never seen a way to do it. You can hide both row/col header or show both but not either-or according to the DocumentFormat.OpenXml.Spreadsheet.PrintOptions. Basically the ShowHeaders option you and Richardo talked about.
The only decent work around I can think of is to fake it with something like this. This includes setting the first row as a repeat. I set the first row as frozen as well but this is optional:
using (var pck = new ExcelPackage(fi))
{
var wb = pck.Workbook;
var ws = wb.Worksheets.Add("Sheet1");
//Make sure headers are not show
ws.PrinterSettings.ShowHeaders = false;
//Header
ws.Cells[1, 1].Value = "A";
ws.Cells[1, 2].Value = "B";
ws.Cells[1, 3].Value = "C";
ws.Cells[1, 4].Value = "D";
var headerrange = ws.Cells[1, 1, 1, 4];
headerrange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
headerrange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
headerrange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
headerrange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
headerrange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
ws.View.FreezePanes(1,4);
ws.PrinterSettings.RepeatRows = new ExcelAddress("$1:$1");
//Some data > 1 page
for (var i = 0; i < 1000; i++)
{
ws.Cells[2 + i, 1].Value = DateTime.Now.AddDays(i);
ws.Cells[2 + i, 2].Value = i;
ws.Cells[2 + i, 3].Value = i*100;
ws.Cells[2 + i, 4].Value = Path.GetRandomFileName();
}
//Save it
pck.Save();
}
Which gives this in the output:
And this in print preview (I scrolled down a few pages):
There is a property called ShowHeaders in the PrinterSettings object that should do what you need. You don't seem to have it there so you might be missing that.
customerWorksheet.PrinterSettings.ShowHeaders = false;
customerWorksheet.View.ShowHeaders = false;
You can see this more explicitly in the source code in the ExcelPrinterSettings class.
/// <summary>
/// Print headings (column letter and row numbers)
/// </summary>
public bool ShowHeaders
{
get
{
return GetXmlNodeBool(_headersPath, false);
}
set
{
SetXmlNodeBool(_headersPath, value, false);
}
}
Hope it helps B. ;)

How to set cell color programmatically epplus?

I was wondering if it is possible to set cell color programmatically using epplus?
I load my data from a sql stored procedure and it works well, but my users want
cells that contain the words 'Annual Leave' to have a background color of light yellow instead of the default white. Is there a way to do this? perhaps by iterating through a datatable perhaps? Below is where
public void ExportTableData(DataTable dtdata)
{
//Using EPPLUS to export Spreadsheets
ExcelPackage pck = new ExcelPackage();
var ws = pck.Workbook.Worksheets.Add("Availability list");
ws.Cells["A1"].LoadFromDataTable(dtdata, true);
ws.Cells["A1:G1"].Style.Font.Bold = true;
ws.Cells["A1:G1"].Style.Font.UnderLine = true;
//change cell color depending on the text input from stored proc?
if (dtdata.Rows[4].ToString() == "Annual Leave")
{
ws.Cells["E1"].Style.Fill.PatternType = ExcelFillStyle.Solid;
ws.Cells["E1"].Style.Fill.BackgroundColor.SetColor(Color.LightYellow);
}
pck.SaveAs(Response.OutputStream);
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", "attachment; filename=Availability.xlsx");
Response.End();
}
Check your line:
if (dtdata.Rows[4].ToString() == "Annual Leave")
If it is a standard .net table wouldnt .ToString() evaluate to "System.Data.DataRow"? Also ws.Cells["E1"] will need to be adjusted for each cell after looping through the row count (basically what krillgar was saying).
Something like that:
[TestMethod]
public void Cell_Color_Background_Test()
{
//http://stackoverflow.com/questions/28679602/how-to-set-cell-color-programmatically-epplus
//Throw in some data
var dtdata = new DataTable("tblData");
dtdata.Columns.Add(new DataColumn("Col1", typeof(string)));
dtdata.Columns.Add(new DataColumn("Col2", typeof(int)));
dtdata.Columns.Add(new DataColumn("Col3", typeof(int)));
for (var i = 0; i < 20; i++)
{
var row = dtdata.NewRow();
row["Col1"] = "Available";
row["Col2"] = i * 10;
row["Col3"] = i * 100;
dtdata.Rows.Add(row);
}
//throw in one cell that triggers
dtdata.Rows[10]["Col1"] = "Annual leave";
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
if (existingFile.Exists)
existingFile.Delete();
using (var pck = new ExcelPackage(existingFile))
{
//Using EPPLUS to export Spreadsheets
var ws = pck.Workbook.Worksheets.Add("Availability list");
ws.Cells["A1"].LoadFromDataTable(dtdata, true);
ws.Cells["A1:G1"].Style.Font.Bold = true;
ws.Cells["A1:G1"].Style.Font.UnderLine = true;
//change cell color depending on the text input from stored proc?
//if (dtdata.Rows[4].ToString() == "Annual Leave")
for (var i = 0; i < dtdata.Rows.Count; i++)
{
if (dtdata.Rows[i]["Col1"].ToString() == "Annual leave")
{
ws.Cells[i + 1, 1].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 1, 1].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightYellow);
}
}
pck.Save();
}
Thanks Ernie! I changed it slightly to allow for my header in excel and to also to make sure that the code doesnt start at E1. I used ws.cells[i + 2, 5] to do this. Cheers!
for (var i = 0; i < dtdata.Rows.Count; i++)
{
if (dtdata.Rows[i]["typeName"].ToString() == "Annual Leave")
{
ws.Cells[i + 2, 5].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 2, 5].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightYellow);
}
else if (dtdata.Rows[i]["typeName"].ToString() == "Available")
{
ws.Cells[i + 2, 5].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 2, 5].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGreen);
}
else
{
ws.Cells[i + 2, 5].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 2, 5].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.White);
}
}
You could try using the conditional formatting option in EPPlus
here is their sample
and here is a SO post with someone else who has used this option
Generally using links as answers is not my style but no reason to remake those wheels, if the EPP sample goes away im guessing so did they and if the SO sample is gone then im guessing so with this answer.
Hope it helps

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