C# fill data to excel.range not working - c#

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

Related

How to select an entire row and column after finding a cell?

After searching for a value in an Excel range I need to select only the entire row and then get each value found in the columns of the row.
xlRange = xlWorksheet.Range[rangeStart, rangeStop];
var xlCell = xlRange.Find
(
valueToFind
, xlRange.Range[rangeStart]
, Excel.XlFindLookIn.xlValues
, Excel.XlLookAt.xlWhole
, Excel.XlSearchOrder.xlByColumns
, Microsoft.Office.Interop.Excel.XlSearchDirection.xlNext
, false
);
Going from the xlCell.get_Address() I need to get only the full row and maybe the full column of that found cell.
To get the full row or column of xlCell and their values, you can use this code:
var row = xlCell.EntireRow;
object[,] rowValues = row.Value2;
var col = xlCell.EntireColumn;
object[,] colValues = col.Value2;
Be aware that these values are pretty big arrays with the contents of the whole worksheet. If you want only the relevant ones, the following should to the trick:
var cells = xlWorksheet.Cells;
var used = xlWorksheet.UsedRange;
var rowCellFirst = cells[row.Row, 1];
var rowCellLast = cells[row.Row, used.Column + used.Columns.Count - 1];
object[,] rowValuesUsed = xlWorksheet.Range[rowCellFirst, rowCellLast].Value2;
var colCellFirst = cells[1, col.Column];
var colCellLast = cells[used.Row + used.Rows.Count - 1, col.Column];
object[,] colValuesUsed = xlWorksheet.Range[colCellFirst, colCellLast].Value2;

Creating an Excel report based on template

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.

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

Why would there be no data in the visualiser when there is valid data in the DataTable?

I'm trying to build a wrapper for SpreadsheetLight that returns a DataSet from any .xlsx document passed through it. However, I seem to be having a problem with DataRows not being added to a temporary DataTable.
Here's part of the code that parses a worksheet and generates a DataTable from it:
public DataSet ReadToDataSet(string fileName)
{
using (var wb = new SLDocument(fileName))
{
var set = new DataSet(GenerateTitle(wb.DocumentProperties.Title));
foreach (var wsName in wb.GetWorksheetNames())
{
var ws = wb.SelectWorksheet(wsName);
// Select worksheet returns a bool, so if it comes back false, try the next worksheet instead.
if (!ws) continue;
// Statistics gives indecies of the first and last data cells
var stats = wb.GetWorksheetStatistics();
// Create a new DataTable for each worksheet
var dt = new DataTable(wsName);
//var addDataColumns = true;
for (var colIdx = stats.StartColumnIndex; colIdx < stats.EndColumnIndex; colIdx++)
dt.Columns.Add(colIdx.ToString(), typeof(string));
// Scan each row
for (var rowIdx = stats.StartRowIndex; rowIdx < stats.EndRowIndex; rowIdx++)
{
//dt.Rows.Add();
var newRow = dt.NewRow();
// And each column for data
for (var colIdx = stats.StartColumnIndex; colIdx < stats.EndColumnIndex; colIdx++)
{
//if (addDataColumns)
// dt.Columns.Add();
newRow[colIdx - 1] = wb.GetCellValueAsString(rowIdx, colIdx);
//if (colIdx >= stats.EndColumnIndex)
// addDataColumns = false;
}
dt.Rows.Add(newRow);
}
set.Tables.Add(dt);
}
// Debug output
foreach (DataRow row in set.Tables[0].Rows)
{
foreach (var output in row.ItemArray)
{
Console.WriteLine(output.ToString());
}
}
return set;
}
}
Note: SpreadsheetLight indicies start from 1 instead of 0;
Now, I've tried replacing dt.Rows.Add() with new object[stats.EndColumnIndex -1];, as well as a temporary variable from var newRow = dt.NewRow(); and then passing them into the DataTable afterwards, but still get the same end result. The row objects are populating correctly, but aren't transferring to the DataTable at the end.
When you explore the object during runtime, it shows the correct number of rows and columns in the relevant properties. But when you open it up in the DataVisualiser you can only see the columns, no rows.
I must be missing something obvious.
Update
I looped through the resulting table and output the values to the console as a test. All the correct values appear, but the visualiser remains empty:
I guess the question now is, why would there be no data in the visualiser when there is valid data in the DataTable?
Update 2
Added the full method for reference, including a simple set of for loops to loop through all rows and columns in the first DataTable. Note: I also experimented with pulling the column creation out of the loop and even setting the datatypes. Made no difference. Commented code shows the original.
Ok, turns out the problem was most likely from the columns being added. Either there were too many columns for the visualiser to handle (1024) which I find hard to believe, or there was a bug in visual studio that's randomly corrected itself.
There's also a bug in SpreadsheetLight that lists all columns as having data when you call GetWorksheetStatistics(); so I've used a workaround that uses the maximum number of total cells available OR the stats.NumberOfColumns, whichever is the smallest.
Either way, the below code now functions.
public DataSet ReadToDataSet(string fileName)
{
using (var wb = new SLDocument(fileName))
{
var set = new DataSet(GenerateTitle(wb.DocumentProperties.Title));
foreach (var wsName in wb.GetWorksheetNames())
{
var ws = wb.SelectWorksheet(wsName);
// Select worksheet returns a bool, so if it comes back false, try the next worksheet instead.
if (!ws) continue;
// Statistics gives indecies of the first and last data cells
var stats = wb.GetWorksheetStatistics();
// There is a bug with the stats columns. Take the total number of elements available or the columns from the stats table, whichever is the smallest
var newColumnIndex = stats.NumberOfCells < stats.NumberOfColumns
? stats.NumberOfCells
: stats.NumberOfColumns;
// Create a new DataTable for each worksheet
var dt = new DataTable(wsName);
var addDataColumns = true;
// Scan each row
for (var rowIdx = stats.StartRowIndex; rowIdx < stats.EndRowIndex; rowIdx++)
{
var newRow = dt.NewRow();
// And each column for data
for (var colIdx = stats.StartColumnIndex; colIdx < newColumnIndex; colIdx++)
{
if (addDataColumns)
dt.Columns.Add();
newRow[colIdx - 1] = wb.GetCellValueAsString(rowIdx, colIdx);
}
addDataColumns = false;
dt.Rows.Add(newRow);
}
set.Tables.Add(dt);
}
return set;
}
}
Hopefully someone else finds this as a useful reference in the future, either for SpreadsheetLight or DataVisualiser in Visual Studio. If anyone know's of any limits for the visualiser, I'm all ears!

How to read excel list elements (data validation) using C# Excel Interop?

I have an Excel file with a cell that has set "Data Validation" to "List". Thanks to that, this cell is a drop down list. How can I read the elements of this list using C# Excel Interop? I can easily read the currently selected value:
Range range = xlWorkSheet.UsedRange; // Worksheet
string cellValue = (range.Cells[x, y] as Excel.Range).Value2.ToString();
but I cannot read the content of the dropdown list that this cell contains.
As it was stated here How do I read the values of Excel dropdowns or checkboxes from c# or vb.net? there is no easy way to do that, but it is possible to create custom function and do it manually. Below is my function that that reads drop down values into a string list. This function is based on an a question mentioned before, but I added support for formulas on other sheets.
List<string> ReadDropDownValues(Excel.Workbook xlWorkBook, Excel.Range dropDownCell)
{
List<string> result = new List<string>();
string formulaRange = dropDownCell.Validation.Formula1;
string[] formulaRangeWorkSheetAndCells = formulaRange.Substring(1, formulaRange.Length - 1).Split('!');
string[] splitFormulaRange = formulaRangeWorkSheetAndCells[1].Split(':');
Excel.Worksheet xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(formulaRangeWorkSheetAndCells[0]);
Excel.Range valRange = (Excel.Range)xlWorkSheet.get_Range(splitFormulaRange[0], splitFormulaRange[1]);
for (int nRows = 1; nRows <= valRange.Rows.Count; nRows++)
{
for (int nCols = 1; nCols <= valRange.Columns.Count; nCols++)
{
Excel.Range aCell = (Excel.Range)valRange.Cells[nRows, nCols];
if (aCell.Value2 != null)
{
result.Add(aCell.Value2.ToString());
}
}
}
return result;
}

Categories