I'm trying to read and then export a complicated excel sheet using c# and exceldatareader.
Basically, I want to grab specific rows e.g rows 10 to 50 and then iterate over each row and values within the row so that I can compare the values cell by cell.
E.g I grab the row 10 and it has a fixed number of columns so I need to iterate over each cell in this row, grab the first 4 and then after that if I find a a value other than 0 or null, I grab that value then go some fixed number of rows above that particular cell (where the value other than null or 0 was found) and grab another value.
Any help would be appreciated.
Here is what i have so far. I can iterate over the rows I need and then read the first 4 columns and then for the rest I need to only find the specific cells and then go some steps above that particular cell. What will be the most efficient way to solve this?
for (int i=15; i< 25; i++)
{
for( int j=1; j< 20; j++)
{
if(j < 4)
{
Console.WriteLine(dt2.Rows[i][j]);
}
else
{
//if you find any value other than 0 or null grab the position of this cell and then go to a specific cell in top most row.
}
}
}
It sounds like you just need to play with the indexes like so:
for (int i=15; i< 25; i++)
{
for( int j=1; j< 20; j++)
{
if(j < 4)
{
Console.WriteLine(dt2.Rows[i][j]);
}
else
{
if (dt2.Rows[i][j] does not equal 0 or null)
{
Console.WriteLine(dt2.Rows[i-some_fixed_#_of_rows][j]);
}
else
{
...
}
}
}
}
Of course, you also have to check if i-some_fixed_#_of_rows does not take you out of bounds.
Related
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'm using visual studio 2017, C#, Windows Forms to create an index for words in a list of sentences.
I have two datagridview:
dataGridView2: This grid has a single column where each row contains a worded sentence.
dGvTopics: This grid has one column for every word that is repeated in the first sentence (first row) in dataGridView2, the column header text is the word.
Goal: I want to click button to categorize, inserting a row in dGvTopics for each row in dataGridView2 (sentences), place a copy of the sentence as the value for that column if the sentence contains the column header text.
My Code is:
private void btnClassify_Click(object sender, EventArgs e)
{
for (int i = 0; i < dGvTopics.Columns.Count; i++)
{
if (dataGridView2.Rows[i].Cells[0].Value.ToString().Contains(dGvTopics.Columns[i].HeaderText))
{
this.dGvTopics.Rows.Add();
this.dGvTopics.Rows[i].Cells[i].Value = dataGridView2.Rows[i].Cells[0].Value;
}
}
}
We can discuss later why you are doing this at all, there are easier ways :)
You need to understand that there are two dimensions to iterate here, the rows in dataGridView2 and the columns in dGvTopics, this means you will need two looping statements, not just one.
Your current code is looping through the Rows in dataGridView2 but only for the number of columns that are in dGvTopics which is a bit confusing.
PRO TIP: Don't use arbitrary single character variable names that have no meaning. Yes i is ubiquitously used to represent index in code you will find around the web, that doesn't mean it is good practice. i should be reserved for lazy programming where there is a single, single dimension array that you are iterating over, in your example there are 4 different levels of arrays that you accessing, the meaning of i is now ambiguous.
Instead of i, use a meaningful variable name like columnIndex or topicIndex. That way when each line is reviewed in isolation, the code is more self documenting. I would even accept t or c in this code, taking the first initial from the conceptual variable meaning will help spot common errors where the wrong indexer is used for the wrong array.
Yes this make the code wordy and long, but we're not constrained by memory space in the same way as our developer ancestors, this doesn't change the size of the final executable, strive to make your code self-documenting.
If you are programming in a code-memory-constrained environment, like for micro-controllers, or tiny chipsets, then still use meaningful short variables, not arbitrarily selected characters.
Applying the above recommendation highlights this first issue:
for (int columnIndex = 0; columnIndex < dGvTopics.Columns.Count; columnIndex ++)
{
if (dataGridView2.Rows[columnIndex].Cells[0].Value.ToString().Contains(dGvTopics.Columns[columnIndex].HeaderText))
{
this.dGvTopics.Rows.Add();
this.dGvTopics.Rows[columnIndex].Cells[columnIndex].Value = dataGridView2.Rows[columnIndex].Cells[0].Value;
}
}
Now we can see that each iteration is moving down the rows, but across the cells at the same rate, meaning that only the cells in a diagonal formation will even be compared and have a value.
The next issue is that because you are only creating a row when the comparison returns true, this means that the rows in dGvTopics might be less than you are expecting, which means less than the value of i (or columnIndex) which will raise an IndexOutOfRangeException the next successful iteration after any comparison that fails.
You can avoid this problem by iterating over the rows and columns separately and adding one row in dGvTopics for every row in dataGridView2.
We can also make the code clearer by saving a reference to the currentSentence rather than referencing the sentence through the array indexers.
private void btnClassify_Click(object sender, EventArgs e)
{
// remove any existing rows, we will reprocess all records.
this.dGvTopics.Rows.Clear();
// Iterate over the rows in the list of sentences.
for (int rowIndex = 0; rowIndex < dataGridView2.Rows.Count; rowIndex ++)
{
// Create one topic row for every sentence
// row index will always be valid now.
this.dGvTopics.Rows.Add();
// save the sentence value to simplify the comparison code.
string currentSentence = dataGridView2.Rows[rowIndex].Cells[0].Value.ToString();
// iterate over the columns in the topics grid
for (int columnIndex = 0; columnIndex < dGvTopics.Columns.Count; columnIndex ++)
{
if (currentSentence.Contains(dGvTopics.Columns[columnIndex].HeaderText))
{
this.dGvTopics.Rows[rowIndex].Cells[columnIndex].Value = currentSentence;
}
}
}
}
It's not easy to comprehend why you want to do this or how this information will be used. In general for manipulating values in cells we generally recommend that databinding techniques are used instead, that way you do not access rows and cells anymore or but the underlying objects that they represent.
demonstrating this is outside of the scope of this question, but it's an avenue worth researching when you have time.
In solutions like this where there are two grids that represent the same logical component, (in this case each row in each grid represents the same sentence value) the underlying dataobject might be a single list, where one property on the object is the sentence and each topic column is a property on the same object.
Importantly, using databinding means that the next process that needs to use the information that you have displayed or edited in the grids can do so without access to or knowledge about the grids at all... Something to think about ;)
Update
This code may result in many empty cells in the topics grid. We could instead only add rows as they are needed, but to do this will require a lot more effort.
NOTE: Grids render all the cells for each row, In the last couple of rows, there may still be empty cells if at least one of the cells for that row has a value.
private void btnClassify_Click(object sender, EventArgs e)
{
// remove any existing rows, we will reprocess all records.
this.dGvTopics.Rows.Clear();
// Iterate over the rows in the list of sentences.
for (int rowIndex = 0; rowIndex < dataGridView2.Rows.Count; rowIndex ++)
{
// save the sentence value to simplify the comparison code.
string currentSentence = dataGridView2.Rows[rowIndex].Cells[0].Value.ToString();
// iterate over the columns in the topics grid
for (int columnIndex = 0; columnIndex < dGvTopics.Columns.Count; columnIndex ++)
{
if (currentSentence.Contains(dGvTopics.Columns[columnIndex].HeaderText))
{
// first we need to know what row index to add this value into
// that involves another iteration, we could store last index in another structure to make this quicker, but here we will do it from first principals.
bool inserted = false;
for(int lookupRow = 0; lookupRow < this.dGvTopics.Rows.Count; lookupRow ++)
{
// find the first row with a null cell;
if(this.dGvTopics.Rows[columnIndex].Value == null)
{
this.dGvTopics.Rows[lookupRow].Cells[columnIndex].Value = currentSentence;
inserted = true;
break;
}
}
if(!inserted)
{
this.dGvTopics.Rows.Add();
this.dGvTopics.Rows[this.dGvTopics.Rows.Count-1].Cells[columnIndex].Value = currentSentence;
}
}
}
}
}
Many thanks to Mr Chris Schaller,
According to his description, the final code changed as follows after compiling:
private void btnClassify_Click(object sender, EventArgs e)
{
// remove any existing rows, we will reprocess all records.
this.dGvTopics.Rows.Clear();
// Iterate over the rows in the list of sentences.
for (int rowIndex = 0; rowIndex < dataGridView2.Rows.Count; rowIndex++)
{
// save the sentence value to simplify the comparison code.
string currentSentence = dataGridView2.Rows[rowIndex].Cells[0].Value.ToString();
// iterate over the columns in the topics grid
for (int columnIndex = 0; columnIndex < dGvTopics.Columns.Count; columnIndex++)
{
if (currentSentence.Contains(dGvTopics.Columns[columnIndex].HeaderText))
{
// first we need to know what row index to add this value into
// that involves another iteration, we could store last index in another structure to make this quicker, but here we will do it from first principals.
bool inserted = false;
for (int lookupRow = 0; lookupRow < this.dGvTopics.Rows.Count; lookupRow++)
{
// find the first row with a null cell;
if (this.dGvTopics.Rows[lookupRow].Cells[columnIndex].Value == null)
{
this.dGvTopics.Rows[lookupRow].Cells[columnIndex].Value = currentSentence;
inserted = true;
break;
}
}
if (!inserted)
{
this.dGvTopics.Rows.Add();
this.dGvTopics.Rows[this.dGvTopics.Rows.Count - 1].Cells[columnIndex].Value = currentSentence;
}
}
}
}
}
I am trying to export data from c# to excel using the following code:
enter worksheet = workbook.ActiveSheet;
worksheet.Name = "ExportedFromDatGrid";
//Loop through each row and read value from each column.
for (int i = 0; i < dataGridView1.Rows.Count + 1; i++)
{
worksheet.Cells[1, i] = dataGridView1.Columns[i - 1].HeaderText;
}
for (int i = 0; i < dataGridView1.Columns.Count; i++)
{
for (int j = 0; j < dataGridView1.Columns.Count - 1; j++)
{
// Excel index starts from 1,1. As first Row would have the Column headers,
// adding a condition check.
worksheet.Cells[i + 2, j + 1] = dataGridView1.Rows[i].Cells[j].Value.ToString();
}
}
I get the following error:
Index was out of range.Must be non negative and less than the size of the collection. Parameter name: index.
UPDATE I solved the problem by changing this for statement:
for ( int i = -1; i < DataGridView1.Columns.Count; i++)
I think the problem is that many online guides and tutorials exlpain that when you count through Lists<>, Arrays and rows/columns of a Table you need to add +1 because all these object containers have a start index of 0.
As a newcomer it might be hard to figure out at the beginning where you have to place the +1 and especially when you have to. Maybe you were confused because you wanted the total amount of rows as your max definition of i. But as you start your loop with int i = 0 (what is correct, because you dont want to skip the row with the index 0) you start as well at the point 0 and not 1. So there is no need to add +1 to the max breakpoint, because you still go dataGridView1.Rows.Count times (<-- amount how often your loop gets executed) through the rows.
This exception Index was out of range tells you that you wanted to do something with a row, which was out of range. It was out of range because this row's item didnt exist. Let´s say you have 10 rows with the index 0 - 9. Now you start going through them beginning at 0. So after 10 times executing, you went through rows 0 - 9. As dataGridView1.Rows.Count gives you the total amount of rows, in this example 10. But you set as the breakpoint dataGridView1.Rows.Count + 1 so the loop wants to do your task the 11th time with the row that has the index 10, but the index of your last row is 9. So it can't find this row and thats the situation when it gives you the Index out of range execption. Now I hope you understand what went wrong and why.
Try:
sheet.GetRow(rowNumber).CreateCell(columnNumber);
And then fill the cell value.
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
}
}
}
i'm trying to make a program that will scan each column of a guitar tab and play a note when it detects a number. I will do this by creating a char array and have the program scan each collumn of an int value. is this possible? if so, how do i do it?
Create a for or foreach loop that goes through each array element. When the value of interest is found, you play the note.
What will the array contain BESIDES integers? Blanks?
If nothing else, just use an int array to begin with, avoid casting from char. the "No Sound" value could be 0, or -1
But to loop through a 2D array, you would just use a nested for loop
for(int row = 0; rows > rowCount; row++)
{
for(int column = 0; column > columnCount; column ++)
{
note = yourArray[row][colum];
// do something with the note
}
}