I'm trying to get the row count of rows which don't have any value (any of columns)
Sample image of the Excel file I'm using:
Highlighted rows have some values in some columns rest of rows are blank I need to count those rows.
I already used this method
int blankRows = 0;
double notEmpty = 1;
while (notEmpty > 0)
{
string aCellAddress = "A" + (rowIndex++).ToString();
Excel.Range row = excelApp.get_Range(aCellAddress, aCellAddress).EntireRow;
notEmpty = excelApp.WorksheetFunction.CountA(row);
if (notEmpty <= 0)
{
blankRows++;
}
}
but this is very time consuming process when file is large and minimum number of blank rows is there.
One thing that might help would be to find the last column that has data and last row that has data as to limit your search.
This is VBA code snippet, but could be easily transformed to C#:
'iterate through columns to determine which is longest to determine the highest row number.
For i = 1 To 16384 'number of columns in excel
'get the row
rowcount = ws.Cells(Rows.Count, i).End(xlUp).Row
'check to see if it's larger than what it is now, if it is, set the value of lRow.
If rowcount > lrow Then
lrow = rowcount
End If
Next
then use a similar loop to get the last row based on the last row, stepping through each row until the last one to get the last column with data.
You can use those values to limit the range that you're looking through. I'm not sure if it will be any faster, but it might help.
Related
I am trying to get data out of 2 cells on the last line of DataGridView in C#. These lines of code give me Index was out of range. Must be non-negative and less than the size of the collection.
intNumberOfRows = dgvWellsFargo.RowCount;
txtNoOfTransactions.Text = dgvWellsFargo.Rows[intNumberOfRows].Cells[1].Value.ToString();
txtTotal.Text = String.Format("{0:c2}", dgvWellsFargo.Rows[intNumberOfRows].Cells[2].Value.ToString());
As you can see I am trying to use the RowCount for my index which I assume is what I need to use. Is there another parameter that I should be using?
I appreciate any suggestions.
You should use :
intNumberOfRows = dgvWellsFargo.RowCount - 1;
The reason is, if you have 5 rows in your gridview, then the count will be 5 and if you know, row index starts with 0. So dgvWellsFargo.Rows[intNumberOfRows] means dgvWellsFargo.Rows[5], but you have max row index 4
Also, you have 2 cells, so the cell index should be used Cells[0] and Cells[1]
So your final code should be :
int lastRowIndex = dgvWellsFargo.RowCount - 1;
txtNoOfTransactions.Text = dgvWellsFargo.Rows[lastRowIndex].Cells[0].Value.ToString();
txtTotal.Text = String.Format("{0:c2}", dgvWellsFargo.Rows[lastRowIndex].Cells[1].Value.ToString());
I am trying to delete rows from a datatable that have an empty or null cell, at the same time I check if a column has empty cells exceeding a percentage, if it's the case I drop the whole column. I tried proceeding like so:
private DataTable CleanData()
{
var dt = BindData(openFileDialog1.FileName);
for (var j = dt.Columns.Count-1; j >= 0; j--)
{
short count = 0;
for (var i = dt.Rows.Count - 1; i >= 0; i--)
{
if (!string.IsNullOrEmpty(dt.Rows[i][j].ToString())) continue;
count++;
}
var percentage = count * 100.0 / dt.Rows.Count;
if (percentage > 10)
{
dt.Columns.RemoveAt(j);
textFile.Text += " " + j + " ";
}
}
dt.AcceptChanges();
for (var j = dt.Columns.Count - 1; j >= 0; j--)
for (var i = dt.Rows.Count - 1; i >= 0; i--)
{
if (!string.IsNullOrEmpty(dt.Rows[i][j].ToString())) continue;
dt.Rows[i].Delete();
}
dt.AcceptChanges();
return dt;
}
I loop a first time over the datatable cells, then check the percentage of empty cells in a column and if it exceeds 10% I delete that column, then I loop a second time and this time delete each row that has an empty cell, but on the second loop I get an error message (System.Data.DeletedRowInaccessibleException) when it reaches a deleted column index, even though it's supposed to loop on a datatable where those columns aren't there.
Any clue where I messed up ?
Edit: I made the changes proposed but still getting the same error
What I THINK you are running into is an unexpected side-effect of your loop checking % and deleting columns. You are starting with the 0-index column (1st column). Checking and then deleting if empty. Do it in reverse... start with the LAST column and work back to 0 and here is why.
Say you start with a table of 3 columns, so your loop counter is intended to to 0, 1, 2. First cycle through, loop counter 0. You determine data good, no delete. Counter = 1 (2nd column). Determine it needs to be removed due to % empty. Now you delete column[1]. This moves what WAS column[2] and now becomes column[1] and your counter now advances to 2. You never checked what WAS the third column.
If you did in reverse, you start at column[3], check it, find its ok (or not, dont care). Now down 1 to column[2] and determine to remove. So it gets deleted and column[3] is now column[2]. Now you check column[0] and finish no problem.
You are already doing this when checking the ROWS (starting at the end and working back). Same principle applies.
As for your loop on deleting the ROW, I would invert your loops.
Outer loop per ROW (last row first, working back)
{
Inner loop per COLUMN
{
if any single column qualifies to delete the row
{
dt.rows[i].Delete();
break; [break out of the column checking loop]
}
}
[ continue with each ROW]
}
Since your existing outer loop is per column, if you process column 1 and delete row 5, then get to column 2 and try to delete row 5 again, that is your failure.
By checking all columns for a single row FIRST and getting out as soon as one qualifies for deletion, you are done with that row and never need to consider looking at any other columns. Move to the next row for processing.
I am trying to write a method that partitions a DataTable based on a given condition (delegate). My problem is that the condition I'm using always partitions exactly half the DataTable. The condition seems to resolve true for half the DataRows even when NO DataRows should resolve to true.
The method looks like this:
private DataTable PartitionDataTable(DataTable data, Func<DataRow, bool> condition) {
DataTable removedRows = data.Clone();
for(int i = 0; i < data.Rows.Count; i++) {
if(condition(data.Rows[i])){
removedRows.ImportRow(data.Rows[i]);
data.Rows.Remove(data.Rows[i]);
}
}
return removedRows;
}
I call this method using this condition:
DataTable removed = PartitionDataTable(data, (row => DateTimeOffset.Parse(row["timestamp"].ToString()) < baselineTimestamp);
If the highest/max timestamp in the data object (DataTable) is a few minutes earlier than the 'baselineTimestamp', determined using data.Compute("max([timestamp])", String.Empty), then half the records are still partitioned and removed when none of them should be because all of them are < baselineTimestamp.
No idea what's going on. Please help me. The goal is to partition DataRows with timestamps earlier than a given (to the nearest milisecond).
You are removing rows as you iterate over the dataset. So if i=2, then you remove row 2, and row 3 is now row 2. You then increment i, operating on the new row 3 (which was row 4) so you skip the original row 3 altogether.
One trick to resolve this is to iterate backwards since the rows that are shifted are ones that you've already processed:
for(int i = data.Rows.Count-1; i >= 0; i--) {
if(condition(data.Rows[i])){
removedRows.ImportRow(data.Rows[i]);
data.Rows.Remove(data.Rows[i]);
}
}
return removedRows;
}
Every time you remove a row from data it's Rows.Count decreases.
Suppose data contains 10 rows at the start.
You increment i to 5 while you remove 5 rows from data.
On next iteration, i is 6, and data.Rows.Count is 5, the loop terminates.
Since you are removing elements from an array, you have to move backward. If you do it forward, you'll skip half of the elements, and this is why you get half of them back:
DataTable removedRows = data.Clone();
for(int i = data.Rows.Count-1; i >= 0 ; i--) {
if(condition(data.Rows[i])){
removedRows.ImportRow(data.Rows[i]);
data.Rows.Remove(data.Rows[i]);
}
}
return removedRows;
}
I am using the following to save the current row of a datagridview which is the right number index but its not shifting the row after the processing is done.
saveRow = dgStock.CurrentCell.RowIndex;
BindGrid();
if (saveRow != 0 && saveRow < dgStock.Rows.Count)
{
dgStock.Rows[saveRow].Selected = true;
}
Was wondering if anybody has had any experience of this just wondering if it should be CurrentCell as per the way I am saving the index in the first place as it is setitng it to the first row here some reason after the re binding of the grid.
Trying to iterate through the rows and cells on an excel spreadsheet, deleting empty ones. I'm using the following routine to do so.
foreach(Range row in sheet.UsedRange.Rows)
{
for (int i = 0; i < row.Columns.Count; i++)
{
Range cell = row.Cells[1, i + 1];
if (cell.Value == null || String.IsNullOrEmpty(cell.Value.ToString()))
{
cell.Delete();
}
}
}
Which works fine for the first two rows. However, it then seems to go haywire.
The third row is completely empty. Yet as it iterates through the columns, when this loop gets to column "I", it reads a value there. The value is what's actually in row 4, column "J".
After that, it just gets worse, missing whole rows and reading incorrect values from the rows it does find.
I am baffled by this. Is there something obvious that I have missed?
Yes, you are missing something very obvious. You are deleting cells. After that operation, your calculation of which cell to pick doesn't work any more.
If you delete a cell, all other cells will move up. That causes your row.Cells[1, i + 1] to be incorrect. If you for example delete one cell in row 2, the value of the cell in the same column in row 3 will never get checked, since it is in row 2 then.
The direction of shift on deletion may also be a factor - you can control it by passing a parameter to the Delete function.
Simply recheck the same column when you delete one:
foreach (Range row in Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet.UsedRange.Rows)
{
for (int i = 0; i < row.Columns.Count; i++)
{
Range cel = row.Cells[1, i + 1];
if (cel.Value == null || String.IsNullOrEmpty(cel.Value.ToString()))
{
// default shift is up
cel.Delete();
// to shift left use cel.Delete(XlDeleteShiftDirection.xlShiftToLeft);
i--; // this will do
}
}
}