After Albert’s brilliant answer about GridView
s it became clear to me that the original wording of the question led to confusion. The GridView
is just an element on the control – for all extents and purposes it could just be a button that has a value stored in a datatag.
For clarification purposes, I will post the full markup for the page and inner control, as well as the full code in it’s current form.
Here’s the ascx
markup:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyClasses.ascx.cs" Inherits="Utilities_CourseChange_MyClasses" %>
<style>
.hiddenCol {
display: none !important;
}
</style>
<asp:Panel ID="Panel1" runat="server">
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<div style="height: 8px;"></div>
<asp:HiddenField ID="hfTEST" runat="server" />
<span style="width: 100%; display: inline-block; text-align: center; font-size: 8pt;">Tutor Groups</span>
<asp:GridView ID="gvTutorGroups" runat="server" AutoGenerateColumns="False" DataSourceID="sqlTutorGroups" DataKeyNames="TTGP_Group_Code" AllowPaging="True" PageSize="8" EmptyDataText="You have no tutor groups to display." Style="margin: 0 auto; width: 870px;" OnRowDataBound="gvTutorGroups_RowDataBound" OnSelectedIndexChanged="gvTutorGroups_SelectedIndexChanged">
<Columns>
<asp:TemplateField ItemStyle-CssClass="hiddenCol" HeaderStyle-CssClass="hiddenCol">
<ItemTemplate>
<asp:HiddenField runat="server" ID="hfTTGPISN" Value='<%# Eval("TTGP_ISN") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="TTGP_Group_Code" HeaderText="TG Code" />
<asp:BoundField DataField="PRPH_Title" HeaderText="Name" />
<asp:BoundField DataField="TTGP_Start_Date" HeaderText="Start Date" DataFormatString="{0:d}" />
<asp:BoundField DataField="TTGP_End_Date" HeaderText="End Date" DataFormatString="{0:d}" />
</Columns>
</asp:GridView>
<asp:LinkButton ID="lnkDummy" runat="server"></asp:LinkButton>
<asp:SqlDataSource ID="sqlTutorGroups" runat="server" ConnectionString="My connection string" SelectCommand="My silly little database query - has two parameters, and spit out the values for the grid">
<SelectParameters>
<asp:Parameter DefaultValue="<%$ AppSettings:CurrentAcademicYear %>" Type="String" Name="YearRef" />
</SelectParameters>
</asp:SqlDataSource>
<div style="height: 8px;"></div>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Panel>
Now I could pull the GridView
out of the UpdatePanel
but I’m not sure what difference it would make.
In the code behind for the `ASCX’, I have this:
using System;
using System.Data;
using System.Web;
using System.Web.UI.WebControls;
public partial class Utilities_CourseChange_MyClasses : System.Web.UI.UserControl
{
protected void gvTutorGroups_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
//Change the mouse cursor to Hand symbol to show the user the cell is selectable
e.Row.Attributes["onmouseover"] = "this.style.textDecoration='underline';this.style.cursor='Pointer'";
e.Row.Attributes["onmouseout"] = "this.style.textDecoration='none';";
e.Row.Attributes["onclick"] = Page.ClientScript.GetPostBackClientHyperlink(gvTutorGroups, "Select$" + e.Row.RowIndex);
}
}
protected void gvTutorGroups_SelectedIndexChanged(object sender, EventArgs e)
{
foreach (GridViewRow row in gvTutorGroups.Rows)
{
if (row.RowIndex == gvTutorGroups.SelectedIndex)
{
row.CssClass = "rowSelected";
DataRowView dataItem = (DataRowView)row.DataItem; //An unreferenced remnant of a previous attempt that I forgot to delete
HiddenField hfTGIsn = (HiddenField)this.Parent.FindControl("hfTGisn"); //Hidden field on the parent page, NOT the ASCX
hfTGIsn.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
hfTEST.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value; // I've put this in to test whether or not it's an issue with everything being reset, or just passing it up to the parent page that's playing up
}
else
{
row.CssClass = "";
}
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
var loginName = HttpContext.Current.User.Identity.Name.ToLowerInvariant().Trim().Replace("domainName", "");
sqlTutorGroups.SelectParameters.Add("UserName", loginName);
}
}
}
Now, I’m aware this isn’t necessarily the best way of doing it – but it’s a legacy system that we’re maintaining while writing a new system in Blazor to eventually replace it. So I’m not necessarily looking for the best way of doing it, just a way that will work – I’ve lifted half of this code from other parts of the system to pull it into a centralised page.
Now the parent ASPX
currently looks like this:
<%@ Page Title="Course Change Tool" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="ProgCoachChangeRequester.aspx.cs" Inherits="Utilities_CourseChange_ProgCoachChangeRequester" EnableEventValidation="false" %>
<%@ Register Src="~/Utilities/CourseChange/MyClasses.ascx" TagName="Groups" TagPrefix="uc1" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="Server">
<asp:HiddenField ID="hfTGisn" runat="server" />
<div class="content">
<uc1:Groups ID="MyGroups" runat="server"></uc1:Groups>
</div>
<div runat="server" id="testDiv"></div>
</asp:Content>
With the code behind literally being as simple as:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class Utilities_CourseChange_ProgCoachChangeRequester : Page
{
protected void Page_Load(object sender, EventArgs e)
{
hfTGisn.Value = ((HiddenField)MyGroups.FindControl("hfTEST")).Value;
testDiv.InnerText = hfTGisn.Value.ToString();
}
}
Now, everything in the ASCX
seems to be working perfectly – my issue is that, when stepping through the code I can see the values being updated as I’d expect; but the end product in the dev tools window on Chrome indicates that, while the hfTEST
(that hidden field I’d put in the ascx to test the mechanism) value is getting updated, the value for hfTGisn
is not. As is evident in this screencap:
I don’t really care too much how I get the value into the parent ASPX (at this point, for all the hassle it’s giving me, I’m half debating just pulling it all out of the ASCX and just shoving it all into the ASPX) – I just need the value so that I can start writing the rest of the page.
So, what’s the easiest way to pluck the value from within the ascx, to the parent page?
Update with further attempt:
Following the suggestion of another answer, I attempted to utilise the ViewState
, changing the code behind the ASPX
page to:
protected void Page_Load(object sender, EventArgs e)
{
hfTGisn.Value = HfTGisn;
testDiv.InnerText = HfTGisn;
}
public string HfTGisn
{
get
{
return (string)ViewState["hfTGisn"];
}
set
{
ViewState["hfTGisn"] = value;
}
}
And changing the gvTutorGroups_SelectedIndexChanged
method to:
protected void gvTutorGroups_SelectedIndexChanged(object sender, EventArgs e)
{
foreach (GridViewRow row in gvTutorGroups.Rows)
{
if (row.RowIndex == gvTutorGroups.SelectedIndex)
{
row.CssClass = "rowSelected";
HiddenField hfTGIsn = (HiddenField)this.Parent.FindControl("hfTGisn");
hfTGIsn.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
hfTEST.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
ViewState["hfTGisn"] = hfTGIsn.Value;
}
else
{
row.CssClass = "";
}
}
}
But, seemingly the ViewState
isn’t populated in Page_Load
2
Answers
In the case of multiple postbacks you could use
ViewState
.Because web application is stateless, after a request the entire page and its controls are created again and the previous page, controls and their values are lost.
ViewState
helps managing the page’s state.You might try something like this:
In code behind add
HfTGisn
asProgCoachChangeRequester
partial class property.In
Page_Load
addIn
gvTutorGroups_SelectedIndexChanged
event handler addAs Poul Bak stated, you can already access
ascx
controlhfTGisn
and I think it is the same for all other controls, includinghfTTGPISN
.This way selected
ID
fromGridView
is saved inHfTGisn
property (essentially stored inViewState
) and therefor it is not lost.HfTGisn
property’s value is then stored as value ofhfTGisn
hidden field, both ingvTutorGroups_SelectedIndexChanged
event handler and inPage_Load
.You’ve said that
hfTGisn
hidden field’s value is read inPage_Load
, so I suppose you could read it directly from the property. Even more, you maybe don’t even need the hidden field, if its only purpose is to store the selectedID
fromGridView
.This is quick fix, but I think it is more important to find where is the second postback coming from. I have no other idea than debugging every step after
gvTutorGroups_SelectedIndexChanged
is executed.Edit: Maybe the simples solution is to read selected
ID
fromGridView
inPage_Load
, and store it as hidden field’s value.GridView
and other controls store their value inViewState
by default.Edit 2:
Thank you for clarification.
I didn’t manage to reproduce the issue, I’ve successfully passed value from control to parent’s hidden field.
However, I think there is a way to fix the problem you are facing, although the solution is far from good, but it works (at least I hope it will).
All other changes proposed in this answer should be rejected.
I still suspect hidden field’s value is reset after some additional request. Instead of
ViewState
let’s useSession
.ViewState
cannot be shared between page and its controls.Session
can be shared through entire application, during single session.In event handler store
ID
ofGridView
inSession
and name it to be unique (you could store the name somewhere where you can access it easily from any part of code). Don’t forget to store the value to hidden field, too:In
Page_Load
of the parent page store value from session to hidden field and it should be always executed, not only in case of postback:Ok, lots of issues here. And we will try explain a few:
First up, you don’t show the grid view markup. Not the end of the world, but it certainly does incrase the completixty of this page – while simple, introduction of a GridView DOES introduce a signfiicnt amount of learning curve.
Next up:
You don’t mention or note how you are triggering the selected index. Perahps you turned on the "select" button for this row?? (again: a BIG detail here).
Ok, so regardless, we ARE triggering the selected index event.
So, with that in mind, you can get the current grid view row like this with your code:
So, if this is a templated column, you use find control.
So, if this is a bound field, then you MUST use .cells[] array.
Now, VERY often, we want to click on a row, and pass say the database primary key id, but we for sure do NOT want to display that key in the GV.
the best way to deal with that is to use the DataKeys feature. It is simple, easy, and ALSO is nice and secure since we don’t have to hide, or even include that PK row in the grid markup.
So, the way to approach this? Often I just drop in a plane jane regular asp.net button right into the markup.
Say, like this:
(and for demo, I put Hotel Name as a label – templated column, so we have a "mix" of data bound fields, a templated (label), and for good measure a plane jane asp.net button.
So, we have this:
Code to load is this:
And we now have this:
so, now all we have to do is add the code stub for the simple plane jane button click event.
While controls (and buttons) outside of the GV, you can just click on them, disaplay property sheet (or even double click on the button in design view, and you jumped to code behind.
However, you can’t do that for controls inside of the GV, so, we have to flip into markup, and get Visual Studio to wire up that event.
You simple for the button type in onclick=, when you hit "=", the intel-sense pops up, and you THEN can choose to create the click event.
You see this:
So, choose create event – don’t seem like happens, but you now have this:
And flip to code behind, and we can write this code:
output:
As noted, we can NOT use the DataItem property here (to get the full underlying data row).
DataItem property is ONLY valid duriing the data bound event.
However, for the most part, it should not matter.
Since we have the grid view row, we can get ANY value displayed.
We can get the hidden database row PK id here ("id") in my example. Again, VERY nice, since we thus don’t have to hide, try to save, or even include and display the database PK id, but it is managed automatic by the server side – no need to save or shove that value away some place, since once we have the GV row, then we get the row index, and with row index, we get datakeys value based on that row index.
So, now, we can say jump to another page, or even hide the GV in a "div" and then show a Repeater item, or Data View or whatever. And we probably should do that based on that PK id.
So, say on the same page, I dropped in a data repeater.
(but hidden), say this:
Then in my gV button click above, we could add this code:
Now, when I click on a row, I get this:
Or as noted, we could jump to another page.
So, once we have the grid row, from that we can get quite much anything we want – even a re-query of ALL of the row data to display columns etc. not necessary in the grid.
So, in summary:
You can drop in a plane jane button – just use a plane jane click event. You can use "namingcontainer" to get the grid row. Really, probablly nicer and clearner then using the built in selected index event.
You can NOT use DataItem property AFTER the databind() occures it will ALWAYS be null. You CAN use DataItem in and during events during binding (such as row data bound event).
You do NOT need to show, hide, tuck away the row click to get the database PK id that identifies the row – that is what the datakeys setting of GV is for.
Edit: with above in mind?
Ok, so now that we have the index?
We are free to "shove" that index value into the hidden control.
For controls outside of the GV, we simply can reference them directly in our code, and no need for some kind of find control exists.
So, we would do this: