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.
Related
So I've got a spreadsheet where I'm trying to find the index of certain column headers.
What I've found is XlLookAt.xlWhole does not find the value. However, if I use XlLookAt.xlPartit does.
I cannot use xlPart as it does not find the correct match in some instances.
I have confirmed that only AMT_ISSUED is in the cell in the actual spreadsheet and there aren't any white spaces on either end.
Does anyone know why XlLookAt.xlWhole doesn't work . Here's the code I'm using
List<int> columnNumbers = new List<int>();
object misValue = System.Reflection.Missing.Value;
var columnIndex = range.EntireRow.Find("AMT_ISSUED",
misValue, XlFindLookIn.xlValues, XlLookAt.xlWhole,
XlSearchOrder.xlByColumns, XlSearchDirection.xlNext,
false);
var index = columnIndex?.Column ?? 0;
columnNumbers.Add(index);
UPDATE:
I have even done this:
var value = ((Range)range.Cells[1, 4]).Value2.ToString();
var columnIndex = range.EntireRow.Find(value,
misValue, XlFindLookIn.xlValues, XlLookAt.xlWhole,
XlSearchOrder.xlByColumns, XlSearchDirection.xlNext,
false);
var index = columnIndex?.Column ?? 0;
columnNumbers.Add(index);
value has found the correct text but column index is still null.
WORKAROUND (I don't like it but it will get me past this hurdle)
Note: the headings could be in row 1 or 2, if it is in row 2 then row one has the first cell populated
for (int i = 1; i < 3; i++)
{
for (var h = 1; h <= colCount; h++)
{
object cellValue = ((Range)range.Cells[i, h]).Value2;
if ((h== 1 || h==2) && cellValue == null)
{
break;
}
if (columns.Contains(cellValue))
{
columnNumbers.Add(h);
}
}
}
The Range.Find method returns Range object. So, first, you need to obtain the Range and then, if it's not null, retrieve column index:
// Create Excel instance
Excel.Application excel = new Excel.Application { Visible = true };
Excel.Workbook book = excel.Workbooks.Open(#"PATH_TO_FILE");
Excel.Worksheet sheet = book.Sheets[1] as Excel.Worksheet;
// Search in the first row
Excel.Range header = sheet.Range["1:1"].Find("AMT_ISSUED", LookAt: Excel.XlLookAt.xlWhole);
if (header != null)
{
// Header is found
int index = header.Column;
}
else
{
// Header is not found
}
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'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 to import some excel file using epplus
the problem is that some cells contains more than one row (and that cause a problem
My excel look like this (in realite their is more tests (test2,test3....)
I can only get the first column by this algorithm..but it will be more complicated to get the seconde column
//this is the list than contain applications (column 2)
ICollection<Application> applications = new List<Application>();
int i = 0;
for (int j = workSheet.Dimension.Start.Row;
j <= workSheet.Dimension.End.Row;
j=i+1)
{
//this is the object that contain the first column
//and also a list of the second column (foreach domain thei `is a list of applications (column 2)`
Domaine domaine = new Domaine();
i += 1;
//add here and not last row
while (workSheet.Cells[i, 1].Text == "" && i < workSheet.Dimension.End.Row)
{
i++;
}
if (i > workSheet.Dimension.End.Row)
break;
domaine.NomDomaine = workSheet.Cells[i, 1].Text;
domaines.Add(domaine);
}
Edit : in other words is their a way to get the number of rows in one cell , OR a way to duplicate the value of each row in the cell
(for exemple if i have a cell from row 1 to 14 and the row number 5 have value)
how can i duplicate that text to all the rows (that will help me solving the problem)
Those are known as Merged cells. Values from merged cells are stored in the .Value property of the first cell in the merged range. This means we need to do just a little bit more work in order to read the value from a merged cell using EPPlus.
EPPlus provides us with a couple of properties that help us get to the correct reference though. Firstly we can use a cell's .Merge property to find out if it is part of a merged range. Then we can use the the worksheet's .MergedCells property to find the relevant range. It's then just a matter of finding the first cell in that range and returning the value.
So, in summary:
Determine if the cell we need to read from is part of a merged range using .Merge
If so, get the index of the merged range using the worksheet's .MergedCells property
Read the value from the first cell in the merged range
Putting this together we can derive a little helper method to take a worksheet object and row/col indices in order to return the value:
static string GetCellValueFromPossiblyMergedCell(ExcelWorksheet wks, int row, int col)
{
var cell = wks.Cells[row, col];
if (cell.Merge) //(1.)
{
var mergedId = wks.MergedCells[row, col]; //(2.)
return wks.Cells[mergedId].First().Value.ToString(); //(3.)
}
else
{
return cell.Value.ToString();
}
}
Worked example
If I have a domain class like this:
class ImportedRecord
{
public string ChildName { get; set; }
public string SubGroupName { get; set; }
public string GroupName { get; set; }
}
that I wanted to read from a spreadsheet that looked like this:
Then I could use this method:
static List<ImportedRecord> ImportRecords()
{
var ret = new List<ImportedRecord>();
var fInfo = new FileInfo(#"C:\temp\book1.xlsx");
using (var excel = new ExcelPackage(fInfo))
{
var wks = excel.Workbook.Worksheets["Sheet1"];
var lastRow = wks.Dimension.End.Row;
for (int i = 2; i <= lastRow; i++)
{
var importedRecord = new ImportedRecord
{
ChildName = wks.Cells[i, 4].Value.ToString(),
SubGroupName = GetCellValueFromPossiblyMergedCell(wks,i,3),
GroupName = GetCellValueFromPossiblyMergedCell(wks, i, 2)
};
ret.Add(importedRecord);
}
}
return ret;
}
static string GetCellValueFromPossiblyMergedCell(ExcelWorksheet wks, int row, int col)
{
var cell = wks.Cells[row, col];
if (cell.Merge)
{
var mergedId = wks.MergedCells[row, col];
return wks.Cells[mergedId].First().Value.ToString();
}
else
{
return cell.Value.ToString();
}
}
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: