I have been coming across a strange bug/error in my code when attempting to select and pull data from multiple rows in GridView controls.
Background:
In short, I have a GridView item, populated with rows of different equipment items that are "checked in" and available to check out of our system. I want to have the user able to select multiple items and check them out at once, rather than doing it X times.
I am using a check box item on each row to do this, like so: http://i.imgur.com/fPYV2.png
There is a button at the bottom to check the equipment out of our database.
Code:
The code I am using to generate the ASPX page is:
<asp:GridView ID="grdEquipment" runat="server" AutoGenerateColumns="false" ShowHeaderWhenEmpty="true" CssClass="table table-bordered">
<Columns>
<asp:TemplateField HeaderText="Select">
<ItemTemplate>
<asp:CheckBox ID="chkMultiSelect_Out" runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField HeaderText="Equipment Name" DataField="Name" />
<asp:BoundField HeaderText="Category" DataField="Category.Name" />
</Columns>
<EmptyDataTemplate>
There is no equipment to display.
</EmptyDataTemplate>
</asp:GridView>
<div class="well">
<asp:Button ID="btnCheckOut" runat="server" CssClass="btn btn-small btn-inverse" Text="Check Out" OnClick="btnCheckOut_Click" />
</div>
This works and compiles with no problem.
The code running the button to check everything out is:
protected void btnCheckOut_Click(object sender, EventArgs e)
{
int checkoutNumber = 0;
//string id = (sender as Button).Attributes["data-id"].ToString();
SortedBindingList<Tracker.Business.Equipment> eqList = Tracker.Business.EquipmentList.FetchAll();
foreach (GridViewRow row in this.grdEquipment.Rows)
{
CheckBox chkMultiSelect_Out = (CheckBox)row.FindControl("chkMultiSelect_Out") ;
if (chkMultiSelect_Out.Checked)
{
checkoutNumber++;
Tracker.Business.Equipment equip = Tracker.Business.Equipment.GetByIdentification(Int32.Parse(row.ID.ToString()));
eqList.Add(equip);
}
}
//If checkoutNumber is 0, do nothing.
//If checkoutNumber is 1, launch popup for only 1 item
//If checkoutNumber is more than 1, launch popup for multiple items.
if (checkoutNumber == 0)
{
Response.Redirect("~/Equipment/CheckInOut/Default.aspx");
}
else if (checkoutNumber == 1)
{
}
else if (checkoutNumber > 1)
{
}
}
Now, OBVIOUSLY, this code isn't finished. I am debugging as I go to make my life easier.
The important code is the foreach loop where I check each row for the control item, and see if it is checked or not.
The Problem:
When I run the code, and test with some check boxes checked, it looks at all the rows, and ALL of them have "Checked = false" as an attribute. What's worse, I found out later that it doesn't even grab the data from the row, as shown in this screen shot here: http://i.imgur.com/clRuk.png
The text should be "Optimus Prime" or "Switch #1", however it is not! And both are checked when I run the code, yet the code sees only false checked items.
The code runs through the foreach loop for each row, i.e., 3 rows, it runs through the loop 3 times. So it sees each row, yet it is not pulling the data...
Where should I start looking?
I fixed my problem. As Tim Schmelter said, I was postbacking the page on every click, so I changed my code to databind the gridview table only on a page load, and not on a post back. Works now!
Related
I am trying to develop a web page in asp .NET with a GridView. I want the GridView to have an edit and delete button. Following is my markup for GridView:
<asp:GridView ID="EduGrid" CssClass="edugrid table table-striped table-hover table-borderless" runat="server" AutoGenerateColumns="False" GridLines="None" OnRowDeleted="EduGrid_RowDeleted" OnRowDeleting="EduGrid_RowDeleting">
<Columns>
<asp:CommandField SelectText="" ShowSelectButton="True" ControlStyle-CssClass="btn btn-dark btn-sm fa fa-edit" />
<asp:CommandField DeleteText="" ShowDeleteButton="true" ControlStyle-CssClass="btn btn-dark btn-sm fa fa-trash" />
<asp:BoundField DataField="degree" HeaderText="Degree" SortExpression="degree" />
<asp:BoundField DataField="major" HeaderText="Major" SortExpression="major" />
<asp:BoundField DataField="year" HeaderText="Passing Year" SortExpression="year" />
<asp:BoundField DataField="total marks" HeaderText="Total Marks" SortExpression="total marks" />
<asp:BoundField DataField="obt marks" HeaderText="Obtained Marks" SortExpression="obt marks" />
<asp:BoundField DataField="division" HeaderText="Division" SortExpression="division" />
<asp:BoundField DataField="board" HeaderText="Board/University" SortExpression="board" />
</Columns>
<HeaderStyle CssClass="thead-dark" Font-Names="Calibri" Font-Size="Larger" />
<RowStyle Font-Names="calibri" Font-Size="Medium" />
</asp:GridView>
I also have a label on the page "CandId" that contains ID to get records in GridView and delete the records from DataBase. But, as there are more than 1 records for each candidate, I want to delete the records on the base of 2 parameters i.e. ID & Degree (the first column). It deletes on the base of ID only but deletes all the records of that specific ID, but I only want to delete one specific entry. If I provide the degree column as a parameter, it gives an object reference exception. Following is my backend code in c#:
protected void EduGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
GridViewRow row = EduGrid.SelectedRow;
conn = new SqlConnection(connString);
string query = "DELETE FROM [Edu Info] WHERE cnic = #cnic AND degree = #degree";
sqlcmd = new SqlCommand(query, conn);
sqlcmd.Parameters.AddWithValue("#cnic", lblID.Text);
sqlcmd.Parameters.AddWithValue("#degree", row.Cells[1].Text);
try
{
conn.Open();
sqlcmd.ExecuteNonQuery();
lblInfo.Text = "Record deleted successfully.";
lblInfo.Visible = true;
Get_Edu();
}
catch (Exception ex)
{
lblInfo.Text = "An error occurred. Please contact the administrator.";
lblInfo.Visible = true;
}
finally
{
conn.Close();
conn.Dispose();
SqlConnection.ClearAllPools();
}
}
I think the problem is that the method OnRowDeleting is not suitable to work with GridView.SelectedRow and that .SelectedRow can only work in the OnSelectedIndexChanged method. If that is the case and I am right, what should I do to fix it? Can anyone please provide a solution?
Thanks.
Ok, lot of issues here. And I not clear why your data source does NOT include the primary key of the database. And as a result, you should not need to use both cnic and degree to identify a row. I cannot EVER recall having rows in a grid in which I don't have a PK ID. (so, very strange on your part).
However, do keep in mind that the delete event does NOT change the selected index. In fact if you drop in say a button, use commandName/CommandText, you find that the row command fires FIRST (selected index not yet changed), and THEN the selected index event fires.
In your example - index will not change.
However, in that event, you can get the row clicked on with with this:
e.RowIndex
So, you can now go like this:
GridViewRow row = EduGrid.Rows[e.RowIndex];
So, the rest of what you have thus should be ok. So, just keep in mind that for command buttons, and most buttons in the grid, the selected index even (if it were to fire) in fact fires AFTER the button event stub. In some cases then you could in theory move the code to the selected index changed event - but that does not even fire in this case. So selected index is NOT available. Use e.RowIndex to get that row.
Also, if you include the PK from the datbase, then you don't need to use the two columns to identify that row in the datbase, but ONLY the PK row id. And you do NOT have to have the PK even display in the grid.
That's what DataKeyNames = "the PK row column name" is for"
eg:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" >
So, DataKeyNames = "PK FROM database"
Thus in above RowDeleting event, then
e.RowIndex - gets you row index clicked on
e.Keys["ID"] - gets you database PK
So, note how you can (should) try and set the DataKeyNames = "primary key" from database. then you can get with e.Keys["ID"] the PK row id.
I have a grid view and when I click on the button I wanted to do a SQL command to see which Order number was pressed. How do I get the row I have clicked on?
asp
<asp:GridView ID="GridView1" runat="server"
DataKeyNames="No_" AutoGenerateColumns="false" style="color:Black;border-collapse:collapse;margin-right: auto;display: table;text-align: center;" OnPageIndexChanging="MyGrid_PageIndexChanging" OnRowDataBound="MyGrid_RowDataBound" AllowPaging="True" PageSize="20" AllowCustomPaging="False" >
<Columns>
<asp:BoundField DataField="No_" HeaderText="No_Encomenda" />
<asp:BoundField DataField="[Item No_]" HeaderText="Item number" />
<asp:BoundField DataField="Quantity" HeaderText="Quantity" />
<asp:TemplateField HeaderText="Select">
<ItemTemplate>
<asp:CheckBox ID="CheckBox1" runat="server" OnCheckedChanged="CheckBox1_CheckedChanged" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
<PagerSettings Mode="NumericFirstLast" PageButtonCount="4" FirstPageText="First" LastPageText="Last"/>
<PagerStyle CssClass="gridview" HorizontalAlign="Center" VerticalAlign="Middle"/>
</asp:GridView>
cs
protected void ButtonDetails_Click(object sender, EventArgs e)
{
MyGrid.Visible = false;
GridView1.Visible = true;
}
Gridview image:
Well, first of all, in your markup, I don't see a button in your markup. So the picture you have, and the markup don't agree with each other.
There are about 3 great ways to get the row click with a button.
Most common:
Drop in a the button. Say like this:
<asp:TemplateField HeaderText="View">
<ItemTemplate>
<asp:Button ID="cmdView" runat="server" Text="Details" CommandName="Select" />
</ItemTemplate>
</asp:TemplateField>
Note how we added the CommandName = "select". This is a special option that will trigger the Gridivew RowIndexed changed. It ALSO triggers the row command. But, in row command the "selected row event" has not yet triggered. So, I just ignore the row command event, and put your code in the selected index changed event.
protected void MyGrid_SelectedIndexChanged(object sender, EventArgs e)
{
// Get selected row
GridViewRow dG = MyGrid.SelectedRow;
Response.Write("Row index was " + dG.RowIndex.ToString());
// get non templated columns - they appear in cells
Response.Write("2nd colum value = " + dG.Cells[1].Text);
// get templated fields - say a combo box for city
DropDownList CityComboBox = (DropDownList)dG.FindControl("DropDownList1");
Response.Write("City selected from combo box = " + CityComboBox.SelectedValue);
}
So by JUST adding the CommandName = "Select", then this will trigger the SelectedinddexChanged event.
Another way is to use the row command (but you have to pass the row index to that event).
But, a really slick way is to 100% ignore the grid events, and JUST use your button.
You can do it this way:
Drop in the button. But now you can't double click on the button to wire up the click event, but you CAN STILL set the event for the click.
While in the markup you can thus type in OnClick= NOTE VERY careful the intli-sense that pops up - it looks like this:
So in the above choices - choose the create new event - it SEEMS like nothing occurred, but when you flip back to code-behind, you have a nice simple clean, good old regular button click event.
Droop a button - create click event. You can now just 100% ignore the complex GridView events (and that select command etc.).
You do this way now:
protected void Button3_Click(object sender, EventArgs e)
{
Button MyButton = (Button)sender;
GridViewRow dG = (GridViewRow)MyButton.Parent.Parent;
Response.Write("Row index was " + dG.RowIndex.ToString());
// get non templated columns - they appear in cells
Response.Write("2nd colum value = " + dG.Cells[1].Text);
// get templated fields - say a combo box for city
DropDownList CityComboBox = (DropDownList)dG.FindControl("DropDownList1");
Response.Write("City selected from combo box = " + CityComboBox.SelectedValue);
}
I find the above is less hassle, but also LESS learning curve to use. We drop in a button - click event. You just pick up the "sender" into a button, and then get parent.parent which turns out to be the grid row we want.
The first parent is I think some cell divider. In fact I use this .parent trick all the time. Thus buttons are a simple button dropped into the grid markup, and we use the standard button click event and code approach.
But, hey, we really don't care about the GridVieew row command, and we really dont' care about the Selected index changed.
We have a button click. So now you can use this slick trick to save world poverty's, save using complex GridView events, and just code up a simple button like any other button.
And we get FULL USE of the grid view row with this trick.
I have a gridview that is populated from the code behind, and has about 300 rows. When I try to access the page containing it, everything loads, and then about 5 seconds later, the program quits and this error message appears:
If I press continue, the application stops running. However, when I look at the page, all of the data has loaded into the gridview (but of course my links, etc, don't work because the session has stopped running).
If I put less data in the table that populates the gridview, I do not get an error (it works with about 30 rows--I'm not sure the exact point where it becomes too much data). Anyway, since it is the exact same code but just less data, I know that I don't actually have an infinite loop or infinite recursion like the message suggests.
Here is the html for the gridview:
<div id="dvGrid" class="gridTable">
<asp:GridView runat="server" ID="GridView1" OnRowDataBound="GridView1_RowDataBound">
<Columns>
<asp:BoundField DataField="Edit" HtmlEncode="false" HeaderText="" HeaderStyle-Wrap="false" SortExpression="Edit" />
</Columns>
</asp:GridView>
</div>
Here is where it is populated in the code behind (this is in the Page_Load method):
DataTable dt = OpenWeather10Day.DBQueries.GetHistoricalData(_Zip);
dt.Columns["Date"].SetOrdinal(0);
GridView1.DataSource = dt;
_LinkColIndex = dt.Columns["Edit"].Ordinal;
_CommentsColIndex = dt.Columns["Comments"].Ordinal;
GridView1.DataSource = dt.DefaultView;
GridView1.DataBind();
And here is the OnRowDataBound function:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{//remove double edit column and add a div in comments column to make it the correct size
TableCell cell = e.Row.Cells[0];
e.Row.Cells.RemoveAt(0);
e.Row.Cells.RemoveAt(_LinkColIndex);
e.Row.Cells.Add(cell);
if (e.Row.RowType == DataControlRowType.DataRow) {
TableCell commentsCell = e.Row.Cells[_CommentsColIndex];
HtmlGenericControl div = new HtmlGenericControl("DIV");
div.Attributes.Add("class", "innerDiv");
div.InnerHtml = commentsCell.Text;
commentsCell.Text = string.Empty;
commentsCell.Controls.Add(div);
}
}
I have discovered that the error is with my "edit" column. If I delete the edit column from the table and get rid of all of the code related to it, all 300 rows load with no problem and the page is still active. The problem is that the edit column is critical to my page, so that is not a possible solution.
I've looked into pagination on scroll, but I can't find an example/demo that does exactly what I need or is simple enough for me to follow (I'm pretty much a complete beginner). I also don't think that it should be necessary to implement pagination; it's okay if the page takes a few seconds to load. My real problem/question is with the stack overflow that causes the session to quit. I have no idea what it is about the edit column that is causing this to occur, but somehow I need to fix this so that accessing this page doesn't quit the entire session. I am willing to add pagination on scroll if it is the only option, but like I said, I haven't been able to figure it out yet. I'm also not sure that it would fix the problem. I'm happy to post any other code, etc, if it'd be at all helpful!
Can you explain what is in your Edit column? Is it a Url, preformatted HTML? What will it do?
You are seeing 2 Edit columns because you have one added in your GridView, then you are adding a second one when you bind your data. Use the AutoGenerateColumns property to prevent this, then add in your other columns.
Example
<asp:GridView runat="server" ID="GridView1"
AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="Edit" HeaderText="Edit" />
<asp:BoundField DataField="Date" HeaderText="Date" />
<asp:TemplateField HeaderText="Comments">
<ItemTemplate>
<div class="innerDiv">
<%# Eval("Comments") %>
</div>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
You can use a TemplateField to put in the HTML in to your Comments column and get rid of
OnRowDataBound="GridView1_RowDataBound"
I have custom upload control. The control has gridview with the uploaded documents and the first column has checkbox for selecting documents I want to delete by clicking Delete button. The upload control is contained in other UserControls almost everywhere in the application and works properly, except at one control. The problem is when I check some document for deleting, when the code executes the checked property of the checkbox is false.
<asp:GridView ID="gvFiles" runat="server" AutoGenerateColumns="false" ShowHeader="true"
CssClass="DataGrid" Width="100%" OnRowDataBound="gvFiles_RowDataBound">
<HeaderStyle HorizontalAlign="left" CssClass="UploadControlGridHeader" />
<RowStyle CssClass="dlcell" />
<EditRowStyle CssClass="dlsell" />
<Columns>
<asp:TemplateField HeaderText="Delete">
<ItemStyle Width="8%" HorizontalAlign="Center" />
<ItemTemplate>
<asp:CheckBox ID="chkFile" runat="server" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
and this is the delete button event
protected void btnDelete_Click(object sender, EventArgs e)
{
for (int i = 0; i < gvFiles.Rows.Count; i++)
{
CheckBox chk = (CheckBox)gvFiles.Rows[i].Cells[0].FindControl("chkFile");
if (chk.Checked)
{
// If file is marked/checked to be deleted
// then delete it.
DeleteFile(i);
}
}
Session[KEY_VIEWSTATEFILES] = this.FileList;
// Repopulate gridview
BindToGridView();
// Call command to keep same visible screen.
CustomCommandEventArgs command = new CustomCommandEventArgs(COMMAND_FILE_DELETED, null);
OnHappyCommand(command);
}
Not having your DeleteFile(i) code, I can't be sure, but I will take a guess that into that function you remove/delete the row. Am I right? If yes, then it change the index of all your rows, which make you think it is not checked when you check the row that you remember to have checked. You will have to use a backward for loop to fix this.
I've been working for a long time with GridViews and DetailsViews, but yesterday I've come across a new scenario, which I quite do not understand.
I have a GridView with ImageButton (CommandName="Insert") which will change the mode of the DetailsView to Insert. Afterwards I'll look for a DropDownList inside that DetailsView and add some items dynamically. Works fine, but one first the first time I press that ImageButton. If I click on "Cancel" in the DetailsView and press the ImageButton again, the .FindControl() Method returns null. What life cycle problem am I facing here?
I've created this sample: (To make it run in your Visual Studio, just bind a DataSource to the DetailsView, otherwise it will not be rendered)
Markup:
<asp:GridView ID="gvCategory" runat="server" OnRowCommand="gvCategory_RowCommand">
<Columns>
</Columns>
<EmptyDataTemplate>
<asp:ImageButton ImageUrl="~/images/add.png" ID="ibAdd" runat="server" CommandName="Insert" />
</EmptyDataTemplate>
</asp:GridView>
<asp:DetailsView ID="dvCategory" runat="server" Width="150px" AutoGenerateRows="false"
AutoGenerateInsertButton="True" DataSourceID="LinqDataSource1">
<Fields>
<asp:TemplateField HeaderText="foo">
<InsertItemTemplate>
<asp:DropDownList ID="ddlCategory" runat="server" Width="150"></asp:DropDownList>
</InsertItemTemplate>
</asp:TemplateField>
</Fields>
</asp:DetailsView><asp:LinqDataSource ID="LinqDataSource1" runat="server"
ContextTypeName="WebApplication1.DataClasses1DataContext"
TableName="Categories"></asp:LinqDataSource>
Codebehind:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
this.gvCategory.DataBind();
}
}
protected void gvCategory_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Insert")
{
this.dvCategory.ChangeMode(DetailsViewMode.Insert);
DropDownList _ddlCat = (DropDownList)this.dvCategory.FindControl("ddlCategory");
if (_ddlCat != null)
{
_ddlCat.Items.Clear();
_ddlCat.Items.Add(new ListItem() { Text = "-- empty --", Value = "-1" });
}
}
}
I have also tried using a ItemTemplate, and not a InsertItemTemplate, but this results in the same. After using the ChangeMode-Method the DetailsView.CurrentMode == InsertMode. The only thing I can think of is, that the markup is already generated for the ItemTemplate and changing the Mode to InsertMode can't affect the rendered markup, or something like this.
Does anybody have a solution to this? =)
I think you are on the right track. It's hard to tell without seeing all of the code, but basically any time you change the rendering mode of a row in a repeater-type control you need to rebind it so that it's re-rendered. The fact that FindControl is returning NULL means only one thing: THE CONTROL IS NOT THERE. Which means it was not rendered. You can verify this by looking at the control hierarchy.
So, in your handler for Cancel are you rebinding?