Creating an Excel report based on template - c#

I have an Excel template where the format for table header, data section and table footer are specified. These sections might have images, merged cells, etc.
The cells where data needs to be mapped are named cells.
I tried using the EPPlus library for generating the report based on the Excel template.
I used the following snippet to copy the range of cells
var worksheet = destExcelPackage.Workbook.Worksheets.Add("Sheet 1");
var sourceRange = sourceExcelPackage.Workbook.Worksheets.First().Cells["B6:P11"];
sourceRange.Copy(worksheet.Cells["A1"]);
But this didn't make the column widths equal to the source. I had to set the column width to the source width as
var startCol = sourceRange.Start.Column;
var endCol = sourceRange.End.Column;
for (int j = startCol, destCol = 1; j <= endCol; j++, destCol++)
{
worksheet.Column(destCol).Width = sourceExcelPackage.Workbook.Worksheets.First().Column(j).Width;
}
I have the following questions:
Is there a better way to set the column width equal to the source?
The copied cells had an image, but it didn't get copy to the new sheet. How to get the image copied?
How to identify the named cells in the Excel sheet so that I can set value to the cell from some data source?

I have found a way to achieve points 2 and 3 above.
It appears that if the picture is named, it is easy to read it.
So for #2
private static void CopyImage(ExcelPackage sourceExcelPackage, ExcelWorksheet destWorksheet)
{
var image = GetImage("Pic01", sourceExcelPackage);
ExcelPicture pic = destWorksheet.Drawings.AddPicture("Pic01", image.Image);
pic.From.Column = image.From.Column;
pic.From.Row = image.From.Row;
pic.To.Column = image.To.Column;
pic.To.Row = image.To.Row;
var destRow = 1;
var destCol = 1;
pic.SetPosition(destRow, Pixel2MTU(image.From.RowOff), destCol, Pixel2MTU(image.From.ColumnOff));
pic.EditAs = eEditAs.TwoCell;
pic.AdjustPositionAndSize();
}
private static ExcelPicture GetImage(string pictureName, ExcelPackage excelFile)
{
var sheet = excelFile.Workbook.Worksheets.First();
var pic = sheet.Drawings[pictureName] as ExcelPicture;
return pic;
}
private static int Pixel2MTU(int fromRowOff)
{
return fromRowOff / ExcelDrawing.EMU_PER_PIXEL;
}
And for #3
var cell = sourceExcelPackage.Workbook.Names?.Where(item => item.Name==headerName).FirstOrDefault();
Will return the cell which is named as headerName.

Related

Populate an excel sheet column with directory files using OpenXML

I have an excel workbook with two WorkSheets, "Tourist Information" and "Documents". In the "Documents" sheet, I have to fill the "Scanned Document" column with all the file names found in a directory. I don't have to fill any other column except Scanned Document column. I am unable to fill the excel sheet with file names which start from cell reference C3. Could you please help me to populate the column with file names.
"Documents" Sheet is:
My code is:
//Open the Excel file in Read Mode using OpenXML
using (SpreadsheetDocument doc = SpreadsheetDocument.Open(#"C:\TouristRecord.xlsx", true))
{
WorksheetPart documents = GetWorksheetPart(doc.WorkbookPart, "Documents");
Worksheet documentsWorksheet = documents.Worksheet;
IEnumerable<Row> documentsRows = documentsWorksheet.GetFirstChild<SheetData>().Descendants<Row>();
//Loop through the Worksheet rows
foreach (var files in Directory.GetFiles(#"C:\DocumentsFolder"))
{
foreach (Row row in documentsRows)
{
// I am unable to write logic to update the excel sheet value here.
}
}
doc.Save();
}
And GetWorksheetPart method is :
public WorksheetPart GetWorksheetPart(WorkbookPart workbookPart, string sheetName)
{
string relId = workbookPart.Workbook.Descendants<Sheet>().First(s => sheetName.Equals(s.Name)).Id;
return (WorksheetPart)workbookPart.GetPartById(relId);
}
To add a cell to C3 you will need to create a new Cell object, assign it a cell reference of C3, set its value and then add it to the Row that represents row 3 on the sheet. We can wrap that logic into a method like this:
private void AddCellToRow(Row row, string value, string cellReference)
{
//the cell might already exist, if it does we should use it.
Cell cell = row.Descendants<Cell>().FirstOrDefault(c => c.CellReference == cellReference);
if (cell == null)
{
cell = new Cell();
cell.CellReference = cellReference;
}
cell.CellValue = new CellValue(value);
cell.DataType = CellValues.String;
row.Append(cell);
}
If we assume that the current worksheet has a contiguous set of rows then the logic of what to write is pretty straightforward:
Iterate each row in the document
Check if the row index is greater than 2 (as you want to start writing from 3 onwards). If it is:
Grab the 3rd Cell or create it if it doesn't exist.
add the nth element of your file list to the Cell.
Increment n
Iterate the remaining files in your file list (as you may have more files than rows in the original document). For each one:
add a new Row
add a new Cell to the Row with the file name as the cell's value.
Putting that into code you end up with:
using (SpreadsheetDocument doc = SpreadsheetDocument.Open(#"C:\TouristRecord.xlsx", true))
{
WorksheetPart documents = GetWorksheetPart(doc.WorkbookPart, "Documents");
//get the she sheetdata as that's where we need to add rows
SheetData sheetData = documents.Worksheet.GetFirstChild<SheetData>();
IEnumerable<Row> documentsRows = sheetData.Descendants<Row>();
//get all of the files into an array
var filenames = Directory.GetFiles(#"C:\DocumentsFolder");
if (filenames.Length > 0)
{
int currentFileIndex = 0;
// keep the row index in case the rowindex property is null anywhere
// the spec allows for it to be null, in which case the row
// index is one more than the previous row (or 1 if this is the first row)
uint currentRowIndex = 1;
foreach (var documentRow in documentsRows)
{
if (documentRow.RowIndex.HasValue)
{
currentRowIndex = documentRow.RowIndex.Value;
}
else
{
currentRowIndex++;
}
if (currentRowIndex <= 2)
{
//this is row 1 or 2 so we can ignore it
continue;
}
AddCellToRow(documentRow, filenames[currentFileIndex], "C" + currentRowIndex);
currentFileIndex++;
if (filenames.Length <= currentFileIndex)
{
// there are no more files so we can stop
break;
}
}
// now output any files we haven't already output. These will need a new row as there isn't one
// in the document as yet.
for (int i = currentFileIndex; i < filenames.Length; i++)
{
//there are more files than there were rows in the directory, add more rows
Row row = new Row();
currentRowIndex++;
row.RowIndex = currentRowIndex;
AddCellToRow(row, filenames[i], "C" + currentRowIndex);
sheetData.Append(row);
}
}
}
There's an assumption above that the current worksheet has a contiguous set of rows. This might not always be true as the spec allows for empty rows to not be written to the XML. In that case, you could end up with gaps in your output. Imagine the original file has data in rows 1, 2 and 5; in that scenario the foreach would cause you to skip writing to rows 3 and 4. This can be solved by checking the currentRowIndex inside the loop and adding a new Row for any gaps that may occur. I haven't added that code as it's a complication that detracts from the fundamentals of the answer.

How to set value of Excel cell by name instead of using coordinates with EPPlus?

I am able to set cell values using EPPlus:
var template = new FileInfo(Server.MapPath("~/App_Data/my_template.xlsx"));
var pck = new ExcelPackage(template);
var ws = pck.Workbook.Worksheets.First();
ws.Cells[15, 4].Value = 66;
ws.Cells["B1"].Value = 55;
Instead of addressing the target cell by its coordinates I would like
to use a named cell/variable "profits".
ws.Range("profits").Value = 66;
However, EPPlus does not support ws.Range(...).
=> Is it nevertheless possible to do so? How would I write an extension method that provides the wanted functionality?
Related articles/questions:
a) Doc for EPPlus:
http://epplus.codeplex.com
b) How to use named cells/variables in Excel:
https://www.computerhope.com/issues/ch000704.htm
What's the RIGHT way to reference named cells in Excel 2013 VBA? (I know I'm messing this up)
c) Libraries for creating Excel Files with C#
Create Excel (.XLS and .XLSX) file from C#
var profits = pck.Workbook.Names["profits"];
profits.Value = 66;
from
Is there a way to get 'named' cells using EPPlus?
It is also possible to set the values for a named range:
var ws = pck.Workbook.Worksheets.First();
using (var namedRange = pck.Workbook.Names["MyNamedRange"])
{
for (int rowIndex = namedRange.Start.Row; rowIndex <= namedRange.End.Row; rowIndex++)
{
for (int columnIndex = namedRange.Start.Column; columnIndex <= namedRange.End.Column; columnIndex++)
{
ws.Cells[rowIndex, columnIndex].Value = 66;
}
}
}
from
EPPlus, Find and set the value for a Named Range

C# fill data to excel.range not working

I am using Microsoft.Office.Interop.Excel for writing data to excel
Now I'm using below code to fill data to a range of cells. Let's say from P10 to P20
Excel.Worksheet ps_sheet = //current worksheet
string[,] data = new string[11,1];
var r = ps_sheet.Range["P10", "P20"];
r.Value2 = data;
The above code is working properly, but if there's only one cell in the range (e.g. "P20" to "P20") the code won't work, the cell is showing the old data.
Excel.Worksheet ps_sheet = //current worksheet
string[,] data = new string[1,1];
var r = ps_sheet.Range["P20", "P20"];
r.Value2 = data;
The excel.range can have one cell only or have multiple cells, since I'm doing a loop.
Is there any reason why this situation happens and how to fix?
Use only one parameter, second parameter is optional
var r = ps_sheet.Range["P20"];
Worksheet.Range Property
I changed to below code and it works:
if (data.GetLength(0) == 1)
{
r = ps_sheet.Range["P10"];
r.Value2 = data[0, 1];
}
else
{
r = ps_sheet.Range["P10", "P20"];
r.Value2 = data;
}
I specially made a case if the data has only one row...
Don't know if there're any better ways to achieve this

Iterate Through rows in Excel

I am attempting to get all the numbers from the cells in column A from an excel spreadsheet, but I am only getting my header line returned. What am I doing wrong?
static void Main(string[] args)
{
var excel = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = excel.Workbooks.Open(#"C:\Documents\ANIs.xlsx");
Worksheet worksheet = workbook.Worksheets[1];
Range a1 = worksheet.get_Range("$A1");
object rawValue = a1.Value;
string Text = a1.Text;
foreach (Range item in a1.Cells)
{
Console.WriteLine("{1}", rawValue, Text);
}
Console.Read();
}
You are getting only the cell A1, you can get all the cells in column A by using :
Range firstCol = workSheet.Range("A:A");
Because your worksheet.get_Range is only returning the one cell. You need to specify the lower-right cell also to get multiple cells, like this:
worksheet.get_Range("$A1", "$D9");
Check out MSDN

NPOI apply font to entire row of cells

I'm using NPOI to export my data to excel. The problem is I found it really hard for any kind of graphical changes.
This is the method I'm using now to apply bold font to my cells.
//Create new Excel workbook
var workbook = new HSSFWorkbook();
//Create new Excel sheet
var sheet = workbook.CreateSheet();
//Create a header row
var headerRow = sheet.CreateRow(0);
var boldFont = workbook.CreateFont();
boldFont.FontHeightInPoints = 11;
boldFont.FontName = "Calibri";
boldFont.Boldweight = (short)NPOI.SS.UserModel.FontBoldWeight.Bold;
int cellCounter = 0;
//day
var cell = headerRow.CreateCell(cellCounter++);
cell.SetCellValue("Day");
cell.CellStyle = workbook.CreateCellStyle();
cell.CellStyle.SetFont(boldFont);
//month
cell = headerRow.CreateCell(cellCounter++);
cell.SetCellValue("Month");
cell.CellStyle = workbook.CreateCellStyle();
cell.CellStyle.SetFont(boldFont);
//year
cell = headerRow.CreateCell(cellCounter++);
cell.SetCellValue("Year");
cell.CellStyle = workbook.CreateCellStyle();
cell.CellStyle.SetFont(boldFont);
//machine name
cell = headerRow.CreateCell(cellCounter++);
cell.SetCellValue("Machine unique name");
cell.CellStyle = workbook.CreateCellStyle();
cell.CellStyle.SetFont(boldFont); //and so on
Is there a ,,cleaner" way to do this ? Now i have to manually add font for individual cells. I've tried many ways to do this on the internet and nothing seems to be working. Do you have a tested way to apply style to specific column or row ?
OffTopic: If not can you provide me with some good open source libraries with decent documentation and support that allow excel export (learning new dll is a pain but... :) what can you do)?
I'm doing something similar and modified my take on it closer for your use:
private string[] columnHeaders =
{
"Day",
"Month",
"Year",
"Machine Unique Name"
}
private void buildSheet(HSSFWorkbook wb, DataTable data, string sheetName)
{
var cHelp = wb.GetCreationHelper();
var sheet = wb.CreateSheet(sheetName);
HSSFFont hFont = (HSSFFont)wb.CreateFont();
hFont.FontHeightInPoints = 11;
hFont.FontName = "Calibri";
hFont.Boldweight = (short)NPOI.SS.UserModel.FontBoldWeight.Bold;
HSSFCellStyle hStyle = (HSSFCellStyle)wb.CreateCellStyle();
hStyle.SetFont(hFont);
IRow headerRow = sheet.CreateRow(1);
int cellCount = 1;
foreach (string str in columnHeaders)
{
HSSFCell cell = (HSSFCell)headerRow.CreateCell(cellCount);
cell.SetCellValue(cHelp.CreateRichTextString((str)));
cell.CellStyle = hStyle;
cellCount += 1;
}
This iterates over however many headers you want starting at the second cell (cellCount = 1) second row (sheet.CreateRow(1)).

Categories