GridView FindControl returns null when HeaderText is set - asp.net

I have a GridView...
<asp:GridView EnableViewState="true"
ID="grdResults"
runat="server"
CssClass="resultsGrid"
OnRowDataBound="grdResults_OnRowDataBound"
AutoGenerateColumns="false"
HeaderStyle-CssClass="header"
OnRowCommand="grdResults_OnRowCommand">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Label ID="lblView"
runat="server"
Visible="false"
Text="View">
</asp:Label>
<asp:HyperLink ID="hypEdit"
runat="server"
Visible="false"
Text="(Edit)"
CssClass="edit">
</asp:HyperLink>
<asp:LinkButton ID="btnDelete"
runat="server"
Visible="false"
Text="(Delete)"
CssClass="delete"
CommandName="DeleteItem"
OnClientClick="return confirm('Are you sure you want to delete?')">
</asp:LinkButton>
<asp:HyperLink ID="hypSelect"
runat="server"
Visible="false"
Text="(Select)"
CssClass="select">
</asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
This has one static column containing a label two hyperlinks and a link button and also has a number of dynamically generated columns...
private void SetupColumnStructure(IEnumerable<string> columnNames)
{
var columnNumber = 0;
foreach (var columnName in columnNames)
{
var templateColumn = new TemplateField
{
ItemTemplate = new CellTemplate(columnName)
};
grdResults.Columns.Insert(columnNumber, templateColumn);
columnNumber++;
}
}
As part of the OnRowDataBound handler I retrieve one of the controls in the statically column and set some attributes on it...
protected void grdResults_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
.
.
.
var row = e.Row;
var rowData = row.DataItem as Dictionary<string, object>;
if (rowData != null)
{
if ((bool)rowData[displayEditLink])
{
var hypEdit = (HyperLink)row.FindControl("hypEdit");
hypEdit.NavigateUrl = "~/Pages/Edit.aspx?action=Edit&objectType=" + rowData[objectTypeLiteral] + "&id=" + rowData[objectIdLiteral];
hypEdit.Visible = true;
}
}
.
.
.
}
This all works fine but no column names are displayed. So I then modify the SetupColumnStructure method so that the HeaderText is set on the template field like this...
private void SetupColumnStructure(IEnumerable<string> columnNames)
{
var columnNumber = 0;
foreach (var columnName in columnNames)
{
var templateColumn = new TemplateField
{
ItemTemplate = new CellTemplate(columnName),
HeaderText = columnName
};
grdResults.Columns.Insert(columnNumber, templateColumn);
columnNumber++;
}
}
For some reason this one extra line change causes the row.FindControl("hypEdit"); call in the OnRowDataBound handler to return null.Can anyone see something im missing here or has anyone experienced a similar issue?
UPDATE
I've made sure that I'm not referring to a header or footer row here. Also, if I step over the object reference exception this occurs for every item that is in the DataSource.
Not sure if this helps, but as I expected, when I stepped through the code the table has generated all the columns expected but all cells (DataControlFieldCells) contain no controls when the HeaderText is set, yet all expected controls when it isnt set.
All very strange. Let me know if you can spot anything else.

When you added the HeaderText, a new RowType was added to the gridview. You'll need to check what type of row raised the OnRowDataBound event and take the appropriate action. In your case, just checking if the e.Row.RowType is a DataRow should solve your problem:
protected void grdResults_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
if(e.Row.RowType == DataControlRowType.DataRow)
{
if ((bool)rowData[displayEditLink])
{
var hypEdit = (HyperLink)row.FindControl("hypEdit");
hypEdit.NavigateUrl = "~/Pages/Edit.aspx?action=Edit&objectType=" + rowData[objectTypeLiteral] + "&id=" + rowData[objectIdLiteral];
hypEdit.Visible = true;
}
}
}

Its because the control you are searching for is contained within another control. FindControl() does not look inside control collections of controls. You will need to write a recursiveFindControl() method.
Hope this helps a little!

Related

AspxGridView checkbox checked column value

I am using one aspxGridview where I used checkbox. Now I need when I check any of the row particular column value I should get in server side to complete my business logic.
Below is the gridview used:
<dx:ASPxGridView KeyFieldName="PracticeID" ID="ASPxGrd" runat="server" ClientInstanceName="grid"
ClientIDMode="AutoID" AutoGenerateColumns="false" Width="100%" OnSelectionChanged="ASPxGrd_SelectionChanged">
<Columns>
<dx:GridViewDataColumn VisibleIndex="0" Name="CheckBoxColumn">
<DataItemTemplate>
<dx:ASPxCheckBox ID="ASPxCheckBox1" runat="server" OnCheckedChanged="ASPxCheckBox1_CheckedChanged" AutoPostBack="true">
</dx:ASPxCheckBox>
</DataItemTemplate>
</dx:GridViewDataColumn>
<dx:GridViewDataColumn FieldName="PracticeName" Caption="Description" VisibleIndex="1">
<FooterTemplate>
Total:
</FooterTemplate>
</dx:GridViewDataColumn>
</dx:ASPxGridView>
I have tried to use oncheckedevent in checkbox with auto postback true and used code to get selected row like below:
protected void ASPxCheckBox1_CheckedChanged(object sender, EventArgs e)
{
ASPxGridView grid = sender as ASPxGridView;
string currentMasterKey = Convert.ToString(grid.GetMasterRowKeyValue());
}
but getting null value of grid object.
Need help.
In your example you have used DataItemTemplate, so in that case the sender will be the control which is added in that data template i.e ASPxCheckBox and you are casting it to grid bcoz of that it is getting null.
try out below snippet.
protected void ASPxCheckBox1_CheckedChanged(object sender, EventArgs e)
{
ASPxCheckBox checkBox = sender as ASPxCheckBox;
var grid = (checkBox.NamingContainer as DevExpress.Web.ASPxGridView.GridViewDataItemTemplateContainer).Grid;
string currentMasterKey = Convert.ToString(grid.GetMasterRowKeyValue());
}
I found this answer before and it's working fine like below:
for (int i = 0; i < ASPxGrd.VisibleRowCount; i++)
{
ASPxCheckBox chk = ASPxGrd.FindRowCellTemplateControl(i, null, "ASPxCheckBox1") as ASPxCheckBox;
if (chk.Checked)
{
if (i == 0)
{
practiceName = ASPxGrd.GetRowValues(i, "PracticeName").ToString();
}
}
}
using this code i am able to get selected checkbox column value.

Stop Textbox in GridView Losing Data on Postback

I have a GridView which I dynamically add rows to through the button_OnClick event, this adds a new row with the Product name & Product ID from a DropDownList and also contains a column with an empty text box for user input.
My problem is that when I test it and enter data in the text box, then add another Product, the post back causes my data to be lost (the correct number of rows are still there with product names / ids).
<asp:GridView runat="server" ID="grdSelectedProducts" BorderWidth="1px" CellPadding="3" CellSpacing="2" AutoGenerateColumns="False" OnRowDataBound="grdSelectedProducts_OnRowDataBound" ShowHeaderWhenEmpty="True" DataKeyNames="ProductId"
OnRowCommand="grdSelectedProducts_RowCommand" OnRowDeleted="grdSelectedProducts_RowDeleted" OnRowDeleting="grdSelectedProducts_RowDeleting" EmptyDataText="Please select a Product and click 'Add'" EnableViewState="True">
<Columns>
<asp:BoundField DataField="Product" HeaderText="Product" ReadOnly="False"/>
<asp:TemplateField HeaderText="Description">
<ItemTemplate>
<asp:TextBox runat="server" ID="txtDescriptionEntry" Text="" style="width:98% !important" EnableViewState="True"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton runat="server" ID="linkDelete" runat="server" CommandName="Delete" CommandArgument="<%# Container.DataItemIndex %>">Remove</asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="ProductId" HeaderText="ProductId" ReadOnly="False" Visible="False" />
</Columns>
</asp:GridView>
How can I avoid postback scrapping the data on each txtDescriptionEntry that is created? There could be between 0 and an infinite number of these text boxes, so I won't have exact names at any one time.
Edit, as per a comment below I'm including the code for how I add rows to the grid:
private DataTable ProductDataTable
{
get {return ViewState["ProductDataTable"] as DataTable ?? new DataTable(); }
set { ViewState["ProductDataTable"] = value; }
}
private DataTable CreateDataTable(bool isAddingValue, string selectedProduct, string selectedId)
{
// if isAddingValue is FALSE then it isn't from a button click to add a Product, it is just
// a call to create the datatable
DataTable dataTable = ProductDataTable;
if (!dataTable.Columns.Contains("Product"))
{
dataTable.Columns.Add("Product");
dataTable.Columns.Add("Description"); // This column is free format text that the user enters.
dataTable.Columns.Add("ProductId");
}
if (isAddingValue)
{
// Get the data from ViewState
//dataTable = ProductDataTable;
DataRow dataRow;
dataRow = dataTable.NewRow();
dataRow["Product"] = selectedProduct;
dataRow["ProductId"] = selectedId;
dataTable.Rows.Add(dataRow);
}
else
{
grdSelectedProducts.DataSource = null;
grdSelectedProducts.DataSource = ProductDataTable;
grdSelectedProducts.DataBind();
}
// Save the data back to ViewState
ProductDataTable = dataTable;
return dataTable;
}
protected void btnAddProduct_OnClick(object sender, EventArgs e)
{
string selectedProduct = ddlProduct.SelectedItem.Text;
string selectedId = ddlProduct.SelectedValue;
DataTable dataTable = CreateDataTable(true, selectedProduct, selectedId);
grdSelectedProducts.DataSource = dataTable;
grdSelectedProducts.DataBind();
}
protected void grdSelectedProducts_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Delete")
{
if (!string.IsNullOrEmpty(e.CommandArgument.ToString()))
{
int rowIndex = Convert.ToInt32(e.CommandArgument);
DataTable table = CreateDataTable(false, string.Empty, string.Empty);
table.Rows.RemoveAt(rowIndex);
grdSelectedProducts.DataSource = table;
grdSelectedProducts.DataBind();
}
}
}
And in the Page_Load event, if it isn't a PostBack there is also binding of an empty list
grdSelectedProducts.DataSource = new List<Products>();
grdSelectedProducts.DataBind();
I figured this out, not the nicest solution (so I probably wouldn't recommend it) but it got me out of a jam.
I used the OnRowDataBound event so for each row that was being bound to the table I would use the RowIndex to get the appropriate row in the DataTable, I would then assign the text box (also obtained via the index) the value from the DT. Example below.
protected void grdSelectedproducts_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowIndex > -1)
{
TextBox txtDescription = e.Row.FindControl("txtDescriptionEntry") as TextBox;
if (txtDescription != null)
{
DataTable dt = ProductDataTable;
DataRow row = dt.Rows[e.Row.RowIndex] as DataRow;
txtDescription.Text = row[1].ToString();
}
}
}

Getting a value from gridview with checkbox

i have this gridview and im trying to print out the MMBR_PROM_ID of whichever column is checked by the user.
(Default.apsx)
Welcome to ASP.NET!
</h2>
<div style="width: 700px; height: 370px; overflow: auto; float: left;">
<asp:GridView ID="GridView1" runat="server" HeaderStyle-CssClass="headerValue"
onselectedindexchanged="GridView1_SelectedIndexChanged">
<Columns>
<asp:TemplateField HeaderText="Generate">
<ItemTemplate>
<asp:CheckBox ID="grdViewCheck" runat="server" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
<asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Generate" />
</asp:Content>
(Default.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
FrontOffEntities tmpdb = new FrontOffEntities();
List<MMBR_PROM> newListMMBR_Prom = tmpdb.MMBR_PROM.ToList();
GridView1.DataSource = newListMMBR_Prom;
GridView1.DataBind();
}
}
so my goal is when i press generate i want to be able to print out as string all the MMBR_PROM_ID which is checked by the user. im kinda new to aspnet so im having a hard time coping up with the syntax
Per your mentioned requirements you can try the below given code to get the values of the MMBR_PROM_ID from Gridview1 on Generate Button Click.
//For every row in the grid
foreach (GridViewRow r in GridView1.Rows)
{
//Find the checkbox in the current row being pointed named as grdViewCheck
CheckBox chk = (CheckBox)r.FindControl("grdViewCheck");
//Print the value in the reponse for the cells[1] which is MMBR_PROM_ID
if (chk!=null && chk.Checked)
{
Response.Write(r.Cells[1].Text);
}
}
Here, cells[1] refers to the Cell Index of the Specific row, in your case it is MMBR_PROM_ID, which you want to print. Hope this helps!
If you are looking for a comma seperated value of MMBR_PROM_ID, the below mentioned code will work for you.
//Declaration of string variable
string str="";
//For every row in the grid
foreach (GridViewRow r in GridView1.Rows)
{
//Find the checkbox in the current row being pointed named as grdViewCheck
CheckBox chk = (CheckBox)r.FindControl("grdViewCheck");
//Print the value in the reponse for the cells[1] which is MMBR_PROM_ID
if (chk!=null && chk.Checked)
{
Response.Write(r.Cells[1].Text);
//appending the text in the string variable with a comma
str = str + r.Cells[1].Text + ", ";
}
}
//Printing the comma seperated value for cells[1] which is MMBR_PROM_ID
Response.Write(str);

Why do I have to click a button twice in an ASP.NET repeater to get the command to fire?

I have an ASP.NET page with the following 3 main areas:
1 - list of checkboxes on the left for fitlering results
2 - Repeater that displays the matching results in the middle (with a button for each item)
3 - Repeater that displays the selected items on the right
On initial page load the page will show the data bound checkboxes and will show all results (since nothing has been checked in the filters). As the user checks or unchecks the checkboxes, the page will reload and the matching results will change. So far this part works great.
In the Results Repeater, each item has a Button. When the user clicks the button for an item in the Results the idea is that the item will get added to the Selected Repeater on the right. What is happening is that after I check or uncheck filter checkboxes - the first time that I then try and click on the buttons in the Results repeater, nothing happens. The page just reloads. Then if I click the button a second time, the Repeater Command will fire and the item will get added to the Repeater on the right hand side. Then, as long as I don't change any of the checkboxes I can click on one of the command buttons and it will work right away. But if I check one of the checkboxes in the filters area (which causes the Results to get re-bound) then I have to click one of the buttons twice to get it to fire.
I have a sense that this has something to do with ViewState but I have no idea. Does anyone know why this would be happening?
Below is my code for both the ASPX page and the code behind.
ASPX Code:
<h3>Filters</h3>
<asp:Repeater ID="rptTechnologies" runat="server" OnItemDataBound="rptFacet_ItemDataBound">
<HeaderTemplate><h4>Technology</h4></HeaderTemplate>
<ItemTemplate><asp:CheckBox ID="chkFacet" runat="server" AutoPostBack="true" OnCheckedChanged="chkFacet_Changed" /><br /></ItemTemplate>
</asp:Repeater>
<asp:Repeater ID="rptVerticals" runat="server" OnItemDataBound="rptFacet_ItemDataBound">
<HeaderTemplate><h4>Vertical</h4></HeaderTemplate>
<ItemTemplate><asp:CheckBox ID="chkFacet" runat="server" AutoPostBack="true" OnCheckedChanged="chkFacet_Changed" /><br /></ItemTemplate>
</asp:Repeater>
<asp:Repeater ID="rptIndustries" runat="server" OnItemDataBound="rptFacet_ItemDataBound">
<HeaderTemplate><h4>Industry</h4></HeaderTemplate>
<ItemTemplate><asp:CheckBox ID="chkFacet" runat="server" AutoPostBack="true" OnCheckedChanged="chkFacet_Changed" /><br /></ItemTemplate>
</asp:Repeater>
<asp:Repeater ID="rptSolutions" runat="server" OnItemDataBound="rptFacet_ItemDataBound">
<HeaderTemplate><h4>Solution</h4></HeaderTemplate>
<ItemTemplate><asp:CheckBox ID="chkFacet" runat="server" AutoPostBack="true" OnCheckedChanged="chkFacet_Changed" /><br /></ItemTemplate>
</asp:Repeater>
<h3>Results</h3>
<asp:Repeater ID="rptMatchingSlides" runat="server" OnItemDataBound="rptMatchingSlides_ItemDataBound" OnItemCommand="rptMatchingSlides_Command">
<ItemTemplate>
<h4><asp:Literal ID="litName" runat="server"></asp:Literal></h4>
<asp:Button ID="btnSelect" runat="server" Text="Select" CommandName="Select" />
</ItemTemplate>
<SeparatorTemplate><hr /></SeparatorTemplate>
</asp:Repeater>
<h3>Selected</h3>
<asp:Repeater ID="rptSelectedSlides" runat="server" OnItemDataBound="rptSelectedSlides_ItemDataBound">
<ItemTemplate>
<h4><asp:Literal ID="litName" runat="server"></asp:Literal></h4>
</ItemTemplate>
<SeparatorTemplate><hr /></SeparatorTemplate>
</asp:Repeater>
Here is the code behind:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
this.BindData();
}
}
public List<string> SelectedSlides
{
get
{
if (Session["SelectedIDs"] != null)
{
string[] _ids = Session["SelectedIDs"].ToString().Split(new char[] { '|' });
List<String> _retVal = new List<string>();
foreach (string _id in _ids)
{
_retVal.Add(_id);
}
return _retVal;
}
else
{
return new List<string>();
}
}
set
{
//Set the session value
string _val = "";
foreach (string _id in value)
{
if (_val == "")
{
_val = _id;
}
else
{
_val += "|" + _id;
}
}
Session["SelectedIDs"] = _val;
}
}
protected void BindData()
{
//Filters
rptTechnologies.DataSource = Repository.GetTaxonomyItems();
rptTechnologies.DataBind();
rptVerticals.DataSource = Repository.GetTaxonomyItems();
rptVerticals.DataBind();
rptIndustries.DataSource = Repository.GetTaxonomyItems();
rptIndustries.DataBind();
rptSolutions.DataSource = Repository.GetTaxonomyItems();
rptSolutions.DataBind();
this.BindMatchingSlides();
}
protected void BindMatchingSlides()
{
...build list of ids from checkboxes...
rptMatchingSlides.DataSource = Repository.GetMatchingSlides(_selectedIDs);
rptMatchingSlides.DataBind();
}
protected void BindSelectedSlides()
{
if (this.SelectedSlides.Count > 0)
{
rptSelectedSlides.DataSource = this.SelectedSlides;
rptSelectedSlides.DataBind();
}
else
{
divSelectedSlides.Visible = false;
}
}
protected void rptMatchingSlides_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
Literal _litName = (Literal)e.Item.FindControl("litName");
Button _btnSelect = (Button)e.Item.FindControl("btnSelect");
_litName.Text = ...set name here...
_btnSelect.CommandArgument = ...use unique ID of item from database...
_btnSelect.ID = "btnSelect_" + e.Item.ItemIndex;
}
}
protected void rptMatchingSlides_Command(object sender, RepeaterCommandEventArgs e)
{
if (e.CommandName == "Select")
{
Item _slide = ...get data from database based on Command Argument...
if (_slide != null)
{
List<string> _selectedSlides = this.SelectedSlides;
_selectedSlides.Add(_slide.ID.ToString());
this.SelectedSlides = _selectedSlides;
}
this.BindSelectedSlides();
}
}
Thanks to Jeremy - removing the line of code where I was setting the ID fixed it. Doh! Somewhere else I had read that you needed to set a unique value for the IDs of the buttons in a repeater. So that must have been the culprit. Thanks to Jeremy.

Get GridView selected row DataKey in Javascript

I have GridView which I can select a row. I then have a button above the grid called Edit which the user can click to popup a window and edit the selected row. So the button will have Javascript code behind it along the lines of
function editRecord()
{
var gridView = document.getElementById("<%= GridView.ClientID %>");
var id = // somehow get the id here ???
window.open("edit.aspx?id=" + id);
}
The question is how do I retrieve the selected records ID in javascript?
I worked it out based on JasonS response. What I did was create a hidden field in the Grid View like this:
<asp:TemplateField ShowHeader="False">
<ItemTemplate>
<asp:HiddenField ID="hdID" runat="server" Value='<%# Eval("JobID") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField Visible="False">
<ItemTemplate>
<asp:LinkButton ID="lnkSelect" runat="server" CommandName="select" Text="Select" />
</ItemTemplate>
</asp:TemplateField>
Then on the OnRowDataBind have code to set the selected row
protected virtual void Grid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// Click to highlight row
Control lnkSelect = e.Row.FindControl("lnkSelect");
if (lnkSelect != null)
{
StringBuilder click = new StringBuilder();
click.AppendLine(m_View.Page.ClientScript.GetPostBackClientHyperlink(lnkSelect, String.Empty));
click.AppendLine(String.Format("onGridViewRowSelected('{0}')", e.Row.RowIndex));
e.Row.Attributes.Add("onclick", click.ToString());
}
}
}
And then in the Javascript I have code like this
<script type="text/javascript">
var selectedRowIndex = null;
function onGridViewRowSelected(rowIndex)
{
selectedRowIndex = rowIndex;
}
function editItem()
{
if (selectedRowIndex == null) return;
var gridView = document.getElementById('<%= GridView1.ClientID %>');
var cell = gridView.rows[parseInt(selectedRowIndex)+1].cells[0];
var hidID = cell.childNodes[0];
window.open('JobTypeEdit.aspx?id=' + hidID.value);
}
</script>
Works a treat :-)
1) change your javascript function to use a parameter
function editRecord(clientId)
{ ....
2) output the call in your editRecord button... if you want to avoid dealing with the .net generated ids, just use a simple
<input type="button" onclick="editRecord(your-rows-client-id-goes-here)" />
Based off of your comments to #DaveK's response, in javascript you can set the id of a hidden field to the clientId of the selected row when the user selects it. Then have your editRecord function use the value set on the hidden form field.
one could avoid javascript altogether, by setting anchor tags pre-populated with the query string for each row (although this will effect your table layout, it will need only one click rather than 2 from the user)
insert in the gridview template:
<asp:HyperLink runat="server" ID="editLink" Target="_blank"
NavigateURL='<%# Eval("JobID","edit.aspx?id={0}") %>'>
Edit..
</asp:HyperLink>

Resources