I am using ASP.NET and I have one GridView that has a TextBox within an editTemplate, but I am not able to find the control; whenever I try to assign the value to TextBox of GridView I'm getting a NullReferenceException.
My code is as follows:
for (int i = 0; i < grdTransfer.Rows.Count; i++)
{
GridViewRow row = grdTransfer.Rows[i];
if (((CheckBox)row.FindControl("chkSelect")).Checked)
{
count = 1;
(row.FindControl("txtDestLocation") as TextBox).Text = txtLocation.Text;
}
}
Issue: NullReferenceException: object reference not set to the instance of an object.
This means I'm getting null whenever I try to assign the value of GridView TextBox from outside the TextBox at runtime.
What am I doing wrong?
A couple things.
WHere is the error happening, I see two likely places.
Does the grid have a header or footer? You aren't checking for either one?
Why aren't you doing this stuff as part of the GridView.RowDataBound event?
I think you are getting this exception on the very last loop.
Grid Row collection index start from Zero but the count will return the total number of row. So you should use count -1 while looping through...
for (int i = 0; i < grdTransfer.Rows.Count -1; i++)..
I would prefer using foreach(GridViewRow row in grdTransfer.Rows) instead.
Related
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 have a DatagridviewCombobox Column and am creating DatagridviewCombobox Cells on each row and adding items to it.
When I change the value of any (combobox) cell, it throws an exception saying that Datagridviewcombobox cell value is not valid.
and the cell value becomes '1'.
I am working on the datagridview_currentcelldirtystatechange event, but haven't been able to make it work.
The code below is creating rows and filling combobox cells with a sequence numbers.
int _rowLimit =1;
for (int i = _rowLimit - 1; i < _rowLimit; i++)
{
datagridview.Rows.Add();
item = i + 1;
datagridview[myColumn, i].Value = _rowLimit;
DataGridViewComboBoxCell oCell = datagridview.CurrentRow.Cells[myColumn] as DataGridViewComboBoxCell;
oCell.Items.Add(item);
((DataGridViewComboBoxColumn)datagridview.Columns[myColumn]).Items.IndexOf(_rowLimit);
((DataGridViewComboBoxColumn)datagridview.Columns[myColumn]).Items.Insert(index, item);
}
And below is what i am doing in datagridview_currentcelldirtystatechange event:
for (int innerIndex = 0; innerIndex < datagridview.Rows.Count; innerIndex++)
{
long sequence = 3;
long oldSequence = 2;
long tempValue= Convert.ToInt64(datagridview.Rows[innerIndex].Cells[myColumn].Value);
if (tempValue <= sequence && tempValue> oldSequence)
{
datagridview.Rows[innerIndex].Cells[myColumn].Value = tempValue+ 1; // increment the sequence
// value here i am getting is correct , but it doesn't show in the DatagridviewCombobox cell where it gets changed of gridview and the mentioned exception is thrown.
}
Any help would be appreciated.
Thanks.
the error on selectedindexChange value of the combobox cells and the exception of DataGridviewComboboxcell value is not valid .. that automatically did change the selected vlue to '1'..
i fixed this issue by adding the DatagridviewComboBoxColumn property in the designer file.
this.columnName.ValueType = typeof(long);
typeof(long) // this is what what i wanted to show the value in the datagridviewcombobox column.
Issue has now resolved.
Thanks.
I have a situation where I have Multiple GridViews on a page GridView1 - GridView4.
All these GridViews have TextBoxes in the cells, I would like to run a calculation function only on the Column of the TextBox that was Clicked and on that particular GridView.
Example: A user clicks on a TextBox in Column 2 of GridView3 and changes a number value, the OnTextChange event of that particular TextBox should fire to run a calculation and total all the numbers of GridView3 Col2 in a footer label.
I searched and all the other suggestions I found assume you have only 1 grid and that you know what preset Column the calculation is done at IE a prices column, But I don't know what Column info a user will edit in any of my 4 GridViews.
I gave up and resolved to just running the calculation code on the Entire GridView, not just the column edited, but to do this I need to Get the GridView.ID of the Gridview containing the TextBox. Extensive serches came up with how to get the ID of the sender Item but not the GridView.ID that contains the sender.
If anyone can help with either situation, please do so, I would really appreciate it.
As Per the conversations below, I have edited this post to include the Edited answer provided by Tim in the hopes that others may learn from this code:
//This is working code
protected void textBox_TextChanged(Object sender, EventArgs e)
{
TextBox txt = (TextBox)sender;
GridViewRow gvRow = (GridViewRow)txt.NamingContainer;
GridView Grid = (GridView)gvRow.NamingContainer;
// if you need the index of the column of the TextBox
int colIndex = 0;
for (int i = 0; i < gvRow.Cells.Count; i++)
{
if (gvRow.Cells[i] == txt.Parent)
{
colIndex = i;
Msg.Text = "Col: "+colIndex.ToString();
break;
}
}
int gridNum = 0;
switch (Grid.ID)
{
case "GridView1":
gridNum = 1;
break;
case "GridView2":
gridNum = 2;
break;
case "GridView3":
gridNum = 3;
break;
case "GridView4":
gridNum = 4;
break;
}
Label ColTotal = (Label)Grid.FooterRow.FindControl("G" + gridNum + "TotalPP" + colIndex);
double total = 0;
foreach (GridViewRow row in Grid.Rows)
{
TextBox tb = (TextBox)row.FindControl(txt.ID);
double d;
if (double.TryParse(tb.Text, out d))
total += d;
}
ColTotal.Text = total.ToString();
}
As you can see, according to the way the code is set up, the colIndex will help me find the footer label for each column and put it's total in.
You could let all TextBoxes handle the same TextChanged event and use this (untested) code:
protected void textBox_TextChanged(Object sender, EventArgs e)
{
TextBox txt = (TextBox) sender;
GridViewRow gvRow = (GridViewRow) txt.NamingContainer;
GridView gv = (GridView) gvRow.NamingContainer;
// if you need the index of the column of the TextBox
// as commented below you're using .NET 2.0
int colIndex;
for (int i = 0; i < gvRow.Cells.Count; i++)
{
if (gvRow.Cells[i] == txt.Parent)
{
colIndex = i;
break;
}
}
double total = 0;
foreach (GridViewRow row in gv.Rows)
{
TextBox tb = (TextBox)row.FindControl(txt.ID);
double d;
if (double.TryParse(tb.Text, out d))
total += d;
}
var tbFooter = (TextBox) gv.FooterRow.FindControl(txt.ID);
tbFooter.Text = total.ToString();
}
Of course assuming that all TextBoxes are in TemplateFields and the footer TextBox has the same ID. Remember to set AutoPostback to true if you want to postback immediately.
When TextChanged runs, sender is a reference to that textbox. You can use the parent property to find out what is the parent of that textbox. If you chain parents together (something like txt.Parent.Parent, you'll eventually find the GridViewRow, and GridView objects. Since you want to calculate the column, you'd have to find the cell that the control is in first, find the cell's index, then bubble up to the grid, loop through each row in that grid, and grab the values in the matching index. I hope you don't have paging enabled, because that will skew the numbers (since they aren't all present). Not an easy task, but possible.
From a performance perspective, you would get better performance with a client-side solution using JQuery or something else.
I have a ListView which is bound to a DataTable. I would like to iterate over the DataTable's rows and access their data. I figured, to do this, I would simply iterate over the ListViewDataItems in the ListView. To test that I am properly accessing the data, I tried the following code, which should simply print the string at column 0 for each row.
for (int i = 0; i < MyListView.Items.Count; i++)
{
ListViewDataItem item = MyListView.Items[i];
DataRow row = (DataRow) item.DataItem;
Response.Write(row[0]);
}
However, nothing is printed. To verify that the ListView is not empty (which it shouldn't be as the data is properly rendered on my aspx page), I tried this:
Response.Write(MyListView.Items.Count);
This prints the number 16, which is correct as there are 16 rows in my ListView. I'm guessing I'm just not accessing the data correctly. I'd appreciate some insight on this.
The best way is to stop on breakpoint (at line DataRow row = (DataRow) item.DataItem;) and simply to check what you have .
for example like here :http://msdn.microsoft.com/en-us/library/ms173083(v=VS.90).aspx
I decided the best solution was to just iterate over the data directly in the DataTable rather than the ListViewDataItems.
for (int i = 0; i < myTable.Rows.Count; i++)
{
for (int j = 0; j < myTable.Columns.Count; j++)
{
object data = data.Rows[i][j];
// do stuff with data
}
}
For anyone still seeking the correct answer to this question, the following code will work (VB.NET):
Dim di as ListViewDataItem
For Each di in MyListView.Items
Response.Write(CType(di.FindControl("ExampleLabel"), Label).Text)
Next
Just substitute the Response.Write line with whatever you wanted to do to each list item. The example line is looking for a control called 'ExampleLabel', casts it back to a label then writes the text value onto the page.
Easily adapted to C# for anyone proficient (not I alas).
I'm hoping someone can help with an exception I've inherited. Basically I'm writing the rows in a datagrid to a text file. Which works fine apart from when the row is hidden, when the row is hidden the exception "Index was outside the bounds of the array" is thrown at the line highlighted below. Thanks for any help.
DataRow dr;
for (int i = 0; i < bindingManagerBase.Count; i++)
{bindingManagerBase.Position = i;
dr = ((DataRowView)bindingManagerBase.Current).Row;
bindingManagerBase.SuspendBinding();
try
{
int rowIndex = dr.Table.Rows.IndexOf(dr);
if (!rowsDisplayStatus[rowIndex]) //<---------Exception here "Index was outside the bounds of the array" //Picture below
{
m_Dgv.Rows[rowIndex].Visible = false;
continue;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
finally
{
bindingManagerBase.ResumeBinding();
}
writeData(tw, dr);
iIndex++;
}
You're getting the row index and then trying to use it with rowsDisplayStatus. You can't use the database row index as an index into your collections.
I would change:
if (!rowsDisplayStatus[rowIndex])
to:
if (!rowsDisplayStatus[i])
How is rowsDisplayStatus populated? If it only contains one element and something is expected to be at index 9, you should take a look at the code that populates it.
As the picture shows, rowsDisplayStatus has 1 item in it... you're trying to pull the 10th item (or the item at index 9)... that index is out of range.
How is "rowsDisplayStatus" populated? Maybe there is a problem in that routine.
I'll explain what mark said, above. (Don't know how to comment, so putting this in an answer)
He's correct you should change the
if (!rowsDisplayStatus[rowIndex])
Into
if (!rowsDisplayStatus[i])
The reason for this is as follows:
The rowIndex grows even when rows where previously deleted from the Rows object. So there could be only one or two rows in the dr.Table.Rows, but they could have indexes (indice) for example of 8 and 9 (because previously rows 1 to 7 where erased or for other reasons).
So you get the current rowIndex by checking the bindingManager.Current.RowIndex property.
But your rowsDisplayStatus is a simple array (or ArrayList) with the correct amount of rows according to i. So for row index:8 (the first row) you should look at rowsDisplayStatus[0] (which is the value of i), and for row index:9 (the second row) you should look at rowsDisplayStatus[1]... etc.
HTH, Moshe
Basically, whenever I've received this error the index (or value that I have set) was non-existant at the time it was being referenced.
For example if I have two Items in a ListBox and I tryi to reference a third Item, I will receive an Index Out Of Range exception (many times in the past)...