find the row number of a datatable column containing specific value C# - c#

I have a datatable in C# with a column called Point that contains various integer values. How do I find the row number of the first row that is equal to a specific value. E.g. Maybe I want to find the first time that the number 52 appears in the Point Column and it appears first at row 10. How do I find the value 10?
Note that I want to find the row number and not the value of another column at this position, hence why this question is different to:
Find row in datatable with specific id

A for loop is probably the simplest way. This answer returns the index of the row (row number) in the DataTable which matches a specific value.
int firstRow = 0;
for (int i = 0; i < dt.Rows.Count; i++)
{
var row = dt.Rows[i];
int point = Convert.ToInt32(row["Point"].ToString());
if (point == 52)
{
// i is the first row matching your condition
firstRow = i;
break;
}
}

The following may work for you:
DataTable table = new DataTable("SomeData");
table.Columns.Add("Point", typeof(int));
table.Rows.Add(5);
table.Rows.Add(7);
table.Rows.Add(52);
table.Rows.Add(2);
table.Rows.Add(1);
table.Rows.Add(4);
table.Rows.Add(9);
var row = table.AsEnumerable().Select((r, i) => new { Row = r, Index = i }).Where(x => (int)x.Row["Point"] == 52).FirstOrDefault();
int rowNumber = 0;
if (row != null)
rowNumber = row.Index + 1;
Note that in this example I give the row number, not the index that starts from zero.

Related

How to skip empty cells while reading data from Excel using OpenXML?

I am trying to read data from Excel and store it into a DataTable using OpenXML. I want data in my DataTable as it is in Excel sheet but when there is a empty cell in Excel, it was not looking as expected.
Because code row.Descendants<Cell>().ElementAt(i) skips empty cells while reading data and in DataTable Rows and Columns are stored incorrectly. I resolved this issue using below code but when my excel has more than 26 columns, it is not working as expected and again data are stored in DataTable incorrectly.
(i.e., While reading data from AA, AB, AC columns)
Can someone help me to rewrite this code to handle this issue when there is more than 26 columns.
private static int CellReferenceToIndex(Cell cell)
{
int index = 0;
string reference = cell.CellReference.ToString().ToUpper();
foreach (char ch in reference)
{
if (Char.IsLetter(ch))
{
int value = (int)ch - (int)'A';
index = (index == 0) ? value : ((index + 1) * 26) + value;
}
else
{
return index;
}
}
return index;
}
You can use example below (taken from here and improved by few validations):
public static int GetColumnIndex(this Cell cell)
{
string columnName = string.Empty;
if (cell != null)
{
string cellReference = cell.CellReference?.ToString();
if (!string.IsNullOrEmpty(cellReference))
// Using `Regex` to "pull out" only letters from cell reference
// (leave only "AB" column name from "AB123" cell reference)
columnName = Regex.Match(cellReference, #"[A-Z]{1,3}").Value;
}
// Column name validations (not null, not empty and contains only UPPERCASED letters)
// *uppercasing may be done manually with columnName.ToUpper()
if (string.IsNullOrEmpty(columnName))
throw new ArgumentException("Column name was not defined.", nameof(columnName));
else if (!Regex.IsMatch(columnName, #"^[A-Z]{1,3}$"))
throw new ArgumentException("Column name is not valid.", nameof(columnName));
int index = 0;
int pow = 1;
// A - 1 iteration, AA - 2 iterations, AAA - 3 iterations.
// On each iteration pow value multiplies by 26
// Letter number (in alphabet) + 1 multiplied by pow value
for (int i = columnName.Length - 1; i >= 0; i--)
{
index += (columnName[i] - 'A' + 1) * pow;
pow *= 26;
}
// Index couldn't be greater than 16384
if (index >= 16384)
throw new IndexOutOfRangeException("Index of provided column name (" + index + ") exceeds max range (16384).");
return index;
}
All exception throws you can replace with return -1 and some kind of Log("...") if you have logging. Otherwise you may not be sure what's problem happened and why was returned -1.
Usage is obvious:
var cells = row.Descendants<Cell>();
foreach (Cell cell in cells)
{
int columnIndex = cell.GetColumnIndex();
// Do what you want with that
}
EDIT.
I'm not sure what you're trying to achieve. And what you mean here:
Because code row.Descendants<Cell>().ElementAt(i) skips empty cells...
I didn't see that. Look at example below:
Random ElementAt in range between 0 and Descendants<Cell>().Count() works too and shows both empty and non-empty cells:

Export selected rows from datagridview to excel using spire.XLS & C#

I'm trying to export the manually selected rows of a datagridview on a windows form application to a excel file using spire.XLS and add the sum of the last column in the last row. The datagridview data is like below:
And when I run the code after selecting the 1st & the last rows then the excel should look like:
Here is my code:
void ExportBtnXLSXClick(object sender, EventArgs e)
{
Workbook book = new Workbook();
Worksheet sheet = book.Worksheets[0];
sheet.Name = "Exported from gridview";
//Convert data from datagridview to datatable
DataTable dt=GetDgvToTable(dataGridView1);
//Export datatable to excel
sheet.InsertDataTable(dt, true, 1, 1, -1, -1);
//sheet.InsertDataTable(dt, true, 1, 1, true);
sheet.Range[1,1,sheet.LastRow,sheet.LastColumn].AutoFitColumns();
sheet.AllocatedRange.BorderAround(LineStyleType.Thin, borderColor:ExcelColors.Black);
sheet.AllocatedRange.BorderInside(LineStyleType.Thin, borderColor:ExcelColors.Black);
book.SaveToFile(#"C:\Users\Tamal\Desktop\Spire.XLS C#\Report.xlsx", ExcelVersion.Version2013);
book.Dispose();
MessageBox.Show("Export complete");
}
with the helper method
public DataTable GetDgvToTable(DataGridView dgv)
{
DataTable dt = new DataTable();
//Column
for (int count = 0; count < dgv.Columns.Count; count++)
{
DataColumn dc = new DataColumn(dgv.Columns[count].Name.ToString());
dt.Columns.Add(dc);
}
//Row
for (int count = 0; count < dgv.SelectedRows.Count; count++)
{
DataRow dr = dt.NewRow();
for (int countsub = 0; countsub < dgv.Columns.Count; countsub++)
//for (int countsub = 0; countsub < dgv.SelectedRows.Count; countsub++)
{
dr[countsub] = Convert.ToString(dgv.Rows[count].Cells[countsub].Value);
}
dt.Rows.Add(dr);
}
decimal total = dataGridView1.SelectedRows.OfType<DataGridViewRow>()
.Sum(t => Convert.ToDecimal(t.Cells[2].Value));
dt.Rows.Add(total);
return dt;
}
But it is not showing the two rows that I selected and also the sum is showing on the 1st column of the last row in excel. How can I get the desired result?
Also, if I run the code multiple times then the data should not overwrite but just paste data after the last filled row, preferably keeping a row blank in between.
Here is how the datagridview looks like:
Please help
You have multiple questions here. For starters, if you want to add multiple selections to the same workbook, then you will need to do some things differently…
check to see if the file already exists, if not, then create a new one, if it does exist then you will need to “open” it.
If the file already exists, then, after opening the file, you will need to check if the worksheet "Exported from gridview" exists… if it does exist, then, you will need to find the last used row and start adding the additional rows after that last row.
Finally save the file.
Currently, the code is simply “creating” a new workbook, then the code is overwriting the existing workbook if it already exists. So, if you want to “add” additional selections to the “same” workbook, you will need to add the code that checks if the file exists, and if so, find the next available row on the worksheet to add the additional items to as explained in the previous 3 steps above.
Below is a simple example that works however it was not tested well and I am confident you may need to adjust the code to fit your requirements.
First, in the GetDgvToTable method, it appears the code is grabbing the wrong rows when creating the table….
for (int count = 0; count < dgv.SelectedRows.Count; count++)
{
DataRow dr = dt.NewRow();
for (int countsub = 0; countsub < dgv.Columns.Count; countsub++)
//for (int countsub = 0; countsub < dgv.SelectedRows.Count; countsub++)
{
dr[countsub] = Convert.ToString(dgv.Rows[count].Cells[countsub].Value);
}
dt.Rows.Add(dr);
}
Above, the code is looping through the “number of selected” rows. The problem is on the line…
dr[countsub] = Convert.ToString(dgv.Rows[count].Cells[countsub].Value);
Specifically, at …
dgv.Rows[count].
Here the code is ignoring the “SELECTED” rows. In other words, if the “selected” rows were contiguous “selected” rows starting from the “first” row (0), then it will work. Unfortunately, the first “selected” row may not necessarily be the “first” row in the grid. The code is incorrectly making this assumption.
To fix this, I suggest you loop through the “selected” rows using a foreach loop through grids SelectedRows collection. This will ensure the code uses the “SELECTED” rows. The code change would look something like…
foreach (DataGridViewRow row in dgv.SelectedRows) {
DataRow dr = dt.NewRow();
for (int i = 0; i < row.Cells.Count; i++) {
dr[i] = row.Cells[i].Value.ToString();
}
dt.Rows.Add(dr);
}
Next, you state you want to additional selections to be added to the same worksheet with an empty row between the selections. This was previously discussed above. In the code below, it checks to see if the Excel file already exist and if it does, then opens that file. Next a check is made to see if the worksheet already exists. If it does not exist, then a new one is created.
Next a check is made to see if any “previous” selections have already been added to the worksheet. I am not that familiar with Spire, however, I noted that if you call the method…
sheet.LastRow…
It will return the next available row… EXCEPT if the worksheet is empty. When the worksheet is empty, this will return a value like 65,570. I will leave this to you to possibly figure out a more elegant way to get the next empty row. In this case I simply checked if the sheet.LastRow was over 60000. If the value is over 60000, then I assume the sheet is empty and simply set the next available row to 1. I am confident there is a better way to do this.
Given all this, the changes below to both methods appears to do as you want by adding the additional selection to the same worksheet “below” the previous selections.
public DataTable GetDgvToTable(DataGridView dgv) {
DataTable dt = new DataTable();
//Column
for (int count = 0; count < dgv.Columns.Count; count++) {
DataColumn dc = new DataColumn(dgv.Columns[count].Name.ToString());
dt.Columns.Add(dc);
}
//Row
foreach (DataGridViewRow row in dgv.SelectedRows) {
DataRow dr = dt.NewRow();
for (int i = 0; i < row.Cells.Count; i++) {
dr[i] = row.Cells[i].Value.ToString();
}
dt.Rows.Add(dr);
}
decimal total = dataGridView1.SelectedRows.OfType<DataGridViewRow>()
.Sum(t => Convert.ToDecimal(t.Cells[2].Value));
dt.Rows.Add("","Total",total);
return dt;
}
Then the changes to the Export method.
private void btnExportToExcel_Click(object sender, EventArgs e) {
Workbook book = new Workbook();
if (File.Exists(#"pathToFile\Report.xlsx")) {
book.LoadFromFile(#"pathToFile\Report.xlsx");
}
Worksheet sheet = book.Worksheets["Exported from gridview"];
if (sheet == null) {
sheet = book.CreateEmptySheet("Exported from gridview");
}
//Convert data from datagridview to datatable
DataTable dt = GetDgvToTable(dataGridView1);
//Export datatable to excel
int startRow = sheet.LastRow + 2;
if (startRow > 60000) {
startRow = 1;
}
sheet.InsertDataTable(dt, true, startRow, 1, -1, -1);
sheet.Range[1, 1, sheet.LastRow, sheet.LastColumn].AutoFitColumns();
sheet.AllocatedRange.BorderAround(LineStyleType.Thin, borderColor: ExcelColors.Black);
sheet.AllocatedRange.BorderInside(LineStyleType.Thin, borderColor: ExcelColors.Black);
book.SaveToFile(#"pathToFile\Report.xlsx", ExcelVersion.Version2013);
book.Dispose();
MessageBox.Show("Export complete");
}
I hope this makes sense and helps.

how to check if an Excel cell belongs to a range, and its address relative to the Range

From my C# application, I am using WorkSheet.UsedRange to find the range of data in a WorkSheet.
I need to check if a specific cell C13 belongs to UsedRange or not; and if it belongs, what is its Row Number & Column Number within the Range.
Example, if the Range starts from Row 10 and Col B, then C13 is Row 4 and Col 2 within the Range.
I found a similar question here:
Checking if selected cell is in specific range
The solution given there is Intersect method.
But in C#, Excel.ApplicationClass's Intersect method takes a very high number of parameters, and while I can pass Missing.Value to so many parameters, I want to know if there is an alternate way other than using Intersect method.
Thanks.
Can this work?
private static string GetRelativeAddress(Range cell, Range range)
{
int startRow = range.Row;
int startColumn = range.Column;
int endRow = range.Row + range.Rows.Count - 1;
int endColumn = range.Column + range.Columns.Count - 1;
if (cell.Row >= startRow && cell.Row <= endRow &&
cell.Column >= startColumn && cell.Column <= endColumn)
{
return $"R{cell.Row - startRow + 1}C{cell.Column - startColumn + 1}";
}
return String.Empty;
}

Why the last row never gets read?

Hello well i have one question why the last row never gets read? It dosen´t matter if its only one row in the excel file or 100 rows. The last row never shows up in the List. And i have no clue why....
Here is my Excel File:
and this is my method:
public List<string> getListData(bool skipFirstRow, int numberOfColumns, string filepath)
{
int startpoint = 1;
int cell = 1;
int row = 1;
List<string> stringList = new List<string>();
//Open Excel (Application)
var excelApplication = openExcelApplication();
//Open Excel File
Excel.Workbook excelWorkbook = excelApplication.Workbooks.Open(filepath);
//Get the Worksheets from the file
Excel.Sheets excelSheets = excelWorkbook.Worksheets;
//Select the first Worksheet
Excel.Worksheet worksheet = (Excel.Worksheet)excelSheets.get_Item(1);
if (skipFirstRow == true)
{
startpoint = 2;
}
Excel.Range range = worksheet.get_Range("A" + Convert.ToString(startpoint), Missing.Value);
while ((range.Cells[startpoint, cell] as Excel.Range).Value2 != null)
{
for (int i = 1; i <= numberOfColumns + 1; i++)
{
string sValue = (range.Cells[row, cell] as Excel.Range).Value2.ToString();
stringList.Add(sValue);
cell++;
}
startpoint++;
cell = 1;
row++;
}
closeExcelApplication(excelApplication);
var result =
stringList
.Select((item, index) => new { Item = item, Index = index })
.GroupBy(x => x.Index / numberOfColumns)
.Select(g => string.Join(";", g.Select(x => x.Item)))
.ToList();
return result;
}
I tried it with the debugger and even google. Then i tried it with the last used row stuff but didnt worked.
Excel.Range last = worksheet.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
Excel.Range range = worksheet.get_Range("A1", last);
int lastUsedRow = last.Row;
int lastUsedColumn = last.Column;
Any help or advise would be great so thanks for your time and help.
Your algorithm is buggy.
Let's see what happens when skipFirstRow is true and your Excel sheet has three rows 1, 2 and 3. At the start of the while loop, we have the following situation:
startpoint = 2
row = 1
During the first iteration, your while loop reads the contents of row 1. After the iteration, we have the following situation:
startpoint = 3
row = 2
During the second iteration, your while loop reads the contents of row 2. After the iteration, we have the following situation:
startpoint = 4
row = 3
Since range.Cells[startpoint, cell] is empty, your code stops here. Rows 1 and 2 have been read, row 3 has been ignored.
As you can see, the reason for your problem is that you check the row in startpoint and read the row in row, and when those two differ, you have a problem. Suggested solution: Drop the startpoint variable and use row instead.
The excel sheet display first row number as 1
but internally it starts from 0, may be it is reading all the data,put different test data on last column

Split Gridview values into other column having same header, if row count value exceeds some limit?

I am getting data from SQL DB and displaying those data in Gridview in asp webpage, now I want to spilt those data into other columns based on row values.
For Ex: I have two columns like Employee Id, Employee Name if row values exceeds 100, I want to display remaining data into other columns with same header.
Here, same header means data is coming from same table, I have requirement like I want to display to other column if row value exceeds 100 count.
Why not add another Grid view next to the existing one, which will contain the same columns... plus It's not clear what you mean by 'row values' I based my answer on Row Count exceeds 100.
If what you need is a way to span between 2 columns, it requires lot of work:
https://social.msdn.microsoft.com/Forums/windows/en-US/87004d70-482a-4b86-ba18-371670254b6a/how-to-merge-headers-in-a-datagridview
Concerning the split of the values to 2 columns, following an idea (it will not work in case the second column is >100, it requires some adjustements)
private void button2_Click(object sender, EventArgs e)
{
const int limitRows = 100;
var col1 = new DataGridViewTextBoxColumn();
var col2 = new DataGridViewTextBoxColumn();
var col3 = new DataGridViewTextBoxColumn();
gridProduct.AutoGenerateColumns = false;
gridProduct.RowHeadersVisible = false;
gridProduct.ColumnHeadersVisible = true;
gridProduct.Columns.Add(col1);
gridProduct.Columns.Add(col2);
gridProduct.Columns.Add(col3);
var colIndex = 0;
int rowIndex = 0;
for (int i = 0; i <= 200; i++)
{
if (i == limitRows)
{
colIndex = 1;
rowIndex = 0;
}
else if (i > limitRows)
rowIndex++;
else
rowIndex = gridProduct.Rows.Add();
gridProduct.Rows[rowIndex].Cells[colIndex].Value = string.Format("Val for row {0}", i);
gridProduct.Rows[rowIndex].Cells[2].Value = string.Format("Another val for row {0}", i);
}
}

Categories