I have a excel report and i need to draw charts based on the data in the report. Am able to get the range from a particular column to last filled row like shown below. I have many columns in my report and i need only the data in a particular column like ("c1","
c12"). the column length may vary. it need not be 12. How can i get the range till last filled row of a column.
Excel.Range last1 = xlWorkSheet2.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
oRange = xlWorkSheet2.get_Range("A6", last1);
Try the following code. This works by selecting the top cell in a row, and then searching downwards until the end of the range is found. The range column is simply the range between start and end. Note that this will only find the last contiguous cell in the range, and will not search through blank rows.
Excel.Range start = xlWorkSheet2.Range["A1"];
Excel.Range column;
if (start.Offset[1].Value != null)
column = xlWorkSheet2.Range[start, start.End[Excel.XlDirection.xlDown]];
else
column = start;
The following code will allow you to retrieve the full used range of the column even if there are blank rows. This code works in a similar manner, but searches upwards from the bottom of the used range in the worksheet to find the last cell in the column containing a value.
Excel.Range start = xlWorkSheet2.Range["A1"];
Excel.Range bottom = xlWorkSheet2.Range["A" + (ws.UsedRange.Rows.Count + 1)];
Excel.Range end = bottom.End[Excel.XlDirection.xlUp];
Excel.Range column = xlWorkSheet2.Range[start, end];
Hi found that all the above methods didn't work for what I wanted to do, so here is my solution:
public object GetLastNotEmptyRowOfColumn(string sheet, string column,int startRow,int endRow)
{
try
{
var validColumn = Regex.IsMatch(column, #"^[a-zA-Z]+$");
if(!validColumn)
{
throw new Exception($"column can only a letter. value entered : {column}");
}
xlBook = xlApp.ActiveWorkbook;
xlSheet = xlBook.Sheets[sheet];
xlRange = xlSheet.Range[$"{column}{startRow}", $"{column}{endRow}"];
object[,] returnVal = xlRange.Value;
var rows = returnVal.GetLength(0);
// var cols = returnVal.GetLength(1);
int count = 1;
for (int r = 1; r <= rows; r++)
{
var row = returnVal[r, 1];
if (row == null) break;
count++;
}
//returns an object : {Count:10,Cell:A9}
return= new { Count=count-1, Cell=$"{column}{startRow+count-1}" };
}
catch (Exception ex)
{
......
}
return null;
}
Usage: var response = GetLastNotEmptyRowOfColumn("Sheet1", "A",1,100);
Result:
Related
I'm trying to copy excel data from one sheet to another. Its working fine but the problem is: In the source file if the data doesn't starts from cell A1 (consider the image below), in this case I want to copy data from the cell B5. Here Some header is not required. The actual data starts from Emp ID cell.
What I've tried is, I can provide a textbox to input the cell address into it and than start copying the data from the provided cell address. But this introduces manual intervention. I want it automated. Any help on this is appreciated. Thanks for the help.
Assuming some basic criteria, the following code should do it. The criteria I assume is: 1) if a row contains any merged cells (like your "Some Header") then that isn't the start row. 2) the start cell will contain text in the cell to the right and in the cell below it.
private static bool RowIsEmpty(Range range)
{
foreach (object obj in (object[,])range.Value2)
{
if (obj != null && obj.ToString() != "")
{
return false;
}
}
return true;
}
private static bool CellIsEmpty(Range cell)
{
if (cell.Value2 != null && cell.Value2.ToString() != "")
{
return false;
}
return true;
}
private Tuple<int, int> ExcelFindStartCell()
{
var excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Visible = true;
Workbook workbook = excelApp.Workbooks.Open("test.xlsx");
Worksheet worksheet = excelApp.ActiveSheet;
// Go through each row.
for (int row = 1; row < worksheet.Rows.Count; row++)
{
Range range = worksheet.Rows[row];
// Check if the row is empty.
if (RowIsEmpty(range))
{
continue;
}
// Check if the row contains any merged cells, if so we'll assume it's
// some kind of header and move on.
object mergedCells = range.MergeCells;
if (mergedCells == DBNull.Value || (bool)mergedCells)
{
continue;
}
// Find the first column that contains text in this row.
for (int col = 1; col < range.Columns.Count; col++)
{
Range cell = range.Cells[1, col];
if (CellIsEmpty(cell))
{
continue;
}
// Now check if the cell to the right also contains text.
Range rightCell = worksheet.Cells[row, col + 1];
if (CellIsEmpty(rightCell))
{
// No text in right cell, try the next row.
break;
}
// Now check if cell below also contains text.
Range bottomCell = worksheet.Cells[row + 1, col];
if (CellIsEmpty(bottomCell))
{
// No text in bottom cell, try the next row.
break;
}
// Success!
workbook.Close();
excelApp.Quit();
return new Tuple<int, int>(row, col);
}
}
// Didn't find anything that matched the criteria.
workbook.Close();
excelApp.Quit();
return null;
}
I am working on dynamic Excel creation with the help of EPPlus library and I have an excel which data looks like:
Name EmpCode Department attendance
Prashant 111 CSE 70% for Sep
Prashant 111 CSE 90% for Oct
XYZ 112 HR 50% for Sep
XYZ 112 HR 90% for Oct
What I want is:
if the current EmpCode is equal to the value of next row then merge this both columns so the expected output will be
I am damn sure that each empCode will be repeated only two times.
The code what I have tried:
for (var rowNum = 1; rowNum <= ws.Dimension.End.Row; rowNum++)
{
var row = ws.Cells[string.Format("{0}:{0}", rowNum)];
}
This code will only work if the empcode is repeated twice but you said you're damn sure it will only be repeated twice so it should be okay, just not very scale-able.
Once you get the data in your spreadsheet you have to loop through all the rows in your dataset. At the beginning of the loop you set the range of your current row and at the end of the loop you set the range of your prior row.
If the previous range is set, you evaluate the columns of each row to determine if you should merge the cells together.
using (var p = new OfficeOpenXml.ExcelPackage(new FileInfo(#"c:\FooFolder\Foo.xlsx")))
{
ExcelWorkbook wb = p.Workbook;
ExcelWorksheet ws = wb.Worksheets[1];
//create variable for previous range that will persist through each loop
ExcelRange previousRange = null;
//set position of first column to merge
int mergecellBegin = 1;
//set position of last column to merge
int mergeCellEnd = 3;
//create variable to check the cells of your rows
bool areCellsEqual;
//iterate through each row in the dataset
for (var rowNum = 2; rowNum <= ws.Dimension.End.Row; rowNum++)
{
ExcelRange currentRange = ws.Cells[rowNum, 1, rowNum, mergeCellEnd];
//will skip if we haven't set previous range yet
if (previousRange != null)
{
//reset your check variable
areCellsEqual = true;
//check if all cells in the ranges are qual to eachother
for (int i = 1; i <= mergeCellEnd; i++)
{
//if the cells from the ranges are not equal then set check variable to false and break the loop
if (!currentRange[rowNum, i].Value.Equals(previousRange[rowNum - 1, i].Value))
{
areCellsEqual = false;
break;
}
}
//if all cells from the two ranges match, merge them together.
if (areCellsEqual)
{
//merge each cell in the ranges
for (int i = 1; i <= mergeCellEnd; i++)
{
ExcelRange mergeRange = ws.Cells[rowNum - 1, i, rowNum, i];
mergeRange.Merge = true;
}
}
}
//sets the previous range to the current range to be used in next iteration
previousRange = currentRange;
}
p.Save();
}
I'm using EPPlus to read excel files.
I have a single cell that is part of merged cells. How do I get the merged range that this cell is part of?
For example:
Assume Range ("A1:C1") has been merged.
Given Range "B1" it's Merge property will be true but there isn't a way to get the merged range given a single cell.
How do you get the merged range?
I was hoping for a .MergedRange which would return Range("A1:C1")
There is no such property out of the box but the worksheet has a MergedCells property with an array of all the merged cell addresses in the worksheet and a GetMergeCellId() method which will give you the index for a given cell address.
We can therefore combine these into a little extension method you can use to get the address. Something like this:
public static string GetMergedRangeAddress(this ExcelRange #this)
{
if (#this.Merge)
{
var idx = #this.Worksheet.GetMergeCellId(#this.Start.Row, #this.Start.Column);
return #this.Worksheet.MergedCells[idx-1]; //the array is 0-indexed but the mergeId is 1-indexed...
}
else
{
return #this.Address;
}
}
which you can use as follows:
using (var excel = new ExcelPackage(new FileInfo("inputFile.xlsx")))
{
var ws = excel.Workbook.Worksheets["sheet1"];
var b3address = ws.Cells["B3"].GetMergedRangeAddress();
}
(Note that in the event that you use this method on a multi-celled range it will return the merged cell address for the first cell in the range only)
You can get all merged cells from worksheet, hence
you can find the merged range a specific cell belongs to using the following:
public string GetMergedRange(ExcelWorksheet worksheet, string cellAddress)
{
ExcelWorksheet.MergeCellsCollection mergedCells = worksheet.MergedCells;
foreach (var merged in mergedCells)
{
ExcelRange range = worksheet.Cells[merged];
ExcelCellAddress cell = new ExcelCellAddress(cellAddress);
if (range.Start.Row<=cell.Row && range.Start.Column <= cell.Column)
{
if (range.End.Row >= cell.Row && range.End.Column >= cell.Column)
{
return merged.ToString();
}
}
}
return "";
}
Update:
Turns out that there is a much easier way using EPPLUS, just do the following:
var mergedadress = worksheet.MergedCells[row, column];
For example, if B1 is in a merged range "A1:C1":
var mergedadress = worksheet.MergedCells[1, 2]; //value of mergedadress will be "A1:C1".
2 is the column number because B is the 2nd column.
This will provide you exact width of merged cells:
workSheet.Cells[workSheet.MergedCells[row, col]].Columns
Not a direct answer as Stewart's answer is perfect, but I was lead here looking for a way to get the value of a cell, whether it's part of a larger merged cell or not, so I improved on Stewart's code:
public static string GetVal(this ExcelRange #this)
{
if (#this.Merge)
{
var idx = #this.Worksheet.GetMergeCellId(#this.Start.Row, #this.Start.Column);
string mergedCellAddress = #this.Worksheet.MergedCells[idx - 1];
string firstCellAddress = #this.Worksheet.Cells[mergedCellAddress].Start.Address;
return #this.Worksheet.Cells[firstCellAddress].Value?.ToString()?.Trim() ?? "";
}
else
{
return #this.Value?.ToString()?.Trim() ?? "";
}
}
And call it like this
var worksheet = package.Workbook.Worksheets[i];
var rowCount = worksheet.Dimension.Rows;
var columnCount = worksheet.Dimension.Columns;
for (int row = 1; row <= rowCount; row++)
{
for (int col = 1; col <= columnCount; col++)
{
string val = worksheet.Cells[row, col].GetVal();
}
}
I want (as title states) to programmatically read values from an Excel file. Row by row and then cell by cell, to have the freedom of creating custom collections out of cell's data.
This questions helped me.
But I need more flexible code. Can I for example write (* is just for all columns)
Range range1 = worksheet.get_Range("*1", Missing.Value)
foreach (Range r in range1)
{
string user = r.Text;
string value = r.Value2;
}
And iterate all cells in row 1 as long as there is next.
There must be some elegant way to iterate through rows and cells in C#.
You can rely on the Rows/Columns properties and then iterate through all the contained ranges (Cells). Sample code:
Range range1 = worksheet.Rows[1]; //For all columns in row 1
//Range range1 = worksheet.Columns[1]; //for all rows in column 1
foreach (Range r in range1.Cells) //range1.Cells represents all the columns/rows
{
// r is the range of the corresponding cell
}
Try this:
Excel.Range r = worksheet.get_Range("*1", Missing.Value);
for (int j = 0; j < r.Rows.Count; j++) {
Excel.Range currentCell = r.Rows[j + 1] as Excel.Range;
}
I'm iterating over a row which I got from a range in order to find a specific word in the cell content and then I want to get the column where I find it. For example, if I find the desired content at the 19th place, it means the the excel column is "S".
Here is the code I'm using so far:
Excel.Worksheet xlWorkSheet = GetWorkSheet(currentWorkBook, "sheet");
var row = xlWorkSheet.Rows["5"];
int rowLength = xlWorkSheet.UsedRange.Columns.Count;
Excel.Range currentTitle = row[1]; //in order to iterate only over the 5th row in this example
for (int i = 1; i < rowLength; i++)
{
string title = currentTitle.Value2[1,i];
if (title == null)
{
continue;
}
if (title.Contains(wordToSearch))
{
string column = THIS IS THE QUESTION - WHAT DO I NEED TO WRITE HERE?
Excel.Range valueCell = xlWorkSheet.Range[column + "5"];
return valueCell.Value2;
}
notice the line of string column in which i need to add the code.
As far as you don't want to rely on any calculation, the other option I see is extracting the column letter from Address property, like in the code below:
Excel.Range currentRange = (Excel.Range)currentTitle.Cells[1, i];
string columnLetter = currentRange.get_AddressLocal(true, false, Excel.XlReferenceStyle.xlA1, missing, missing).Split('$')[0];
string title = null;
if (currentRange.Value2 != null)
{
title = currentRange.Value2.ToString();
}
As you can see, I am forcing a "$" to appear in order to ease the column letter retrieval.