Home > Enterprise >  How to update a hidden field's value in code behind
How to update a hidden field's value in code behind

Time:04-26

I know this isn't exactly a new question, but I'm having some grief with it and none of my research seems to be leading to a solution.

I have this rather simple ASPX page:

<%@ 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 >
        <uc1:Groups ID="MyGroups" runat="server"></uc1:Groups>
    </div>

    <div runat="server" id="testDiv"></div>
</asp:Content>

Now, in the code behind for the ASCX that's referenced; I have this method which essentially updates that HiddenField with the ID selected in a GridView:

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;
            HiddenField hfTGIsn = (HiddenField)this.Parent.FindControl("hfTGisn");
            hfTGIsn.Value = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
        }
        else
        {
            row.CssClass = "";
        }
    }
}

My plan is to use the selected ID to generate the rest of the page.

The issue I'm facing is that despite this method working perfectly (I've stepped through it in the debugger and seen the value change), the value doesn't seem to stick - when I try to access it in the ASPX page, the value is non-existent.

What am I missing here? Is it as simple as a setting I haven't spotted?

Update: still not sure on a fix, but it looks like it's essentially doing a double postback. The first sets the value, the second resets it... But I'm not sure how to avoid that second postback.

CodePudding user response:

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:

public string HfTGisn
{
   get
   {
      return (string)ViewState["hfTGisn"];
   }
   set
   {
      ViewState["hfTGisn"] = value;
   }
}

In code behind add HfTGisn as ProgCoachChangeRequester partial class property.

In Page_Load add

hfTGisn.Value = HfTGisn;

In gvTutorGroups_SelectedIndexChanged event handler add

HfTGisn = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
hfTGisn.Value = HfTGisn;

As Poul Bak stated, you can already access ascx control hfTGisn and I think it is the same for all other controls, including hfTTGPISN.

This way selected ID from GridView is saved in HfTGisn property (essentially stored in ViewState) and therefor it is not lost. HfTGisn property's value is then stored as value of hfTGisn hidden field, both in gvTutorGroups_SelectedIndexChanged event handler and in Page_Load.

You've said that hfTGisn hidden field's value is read in Page_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 selected ID from GridView.

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 from GridView in Page_Load, and store it as hidden field's value. GridView and other controls store their value in ViewState by default.

CodePudding user response:

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:

    protected void GridView1_SelectedIndexChanged1(object sender, EventArgs e)
    {

        GridViewRow MyGridRow = GridView1.Rows[GridView1.SelectedIndex];

        // to get NON templated colums of GV, you use:
        Debug.Print(MyGridRow.Cells[0].Text);

        // to get templated columns of GV, you use
        TextBox txtHotelName = MyGridRow.FindControl("txtHotelName") as TextBox;

        // you CAN NOT USE or GET the data time - it is OUT of scope, does not exist
        // DataItme ONLY exists DURING the data bind, NOT after. 

        // however, hftGisn is NOT in the grid view - so you are free to use and referance
        // that control like any other plane jane control on the web page.

        // eg:

        hfTGisn.Value = "some value goes here";

    }

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:

        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CssClass="table"
            DataKeyNames="ID"  OnSelectedIndexChanged="GridView1_SelectedIndexChanged1"  >
            <Columns>
                <asp:BoundField DataField="FirstName" HeaderText="FirstName"  />
                <asp:BoundField DataField="LastName" HeaderText="LastName"    />
                <asp:TemplateField HeaderText="Hotel Name">
                    <ItemTemplate>
                        <asp:Label ID="txtHotel" runat="server"
                            Text = '<%# Eval("HotelName") %>' 
                            Width="100px" >

                        </asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:BoundField DataField="Description" HeaderText="Description" />

                <asp:TemplateField HeaderText="View" ItemStyle-HorizontalAlign="Center">
                    <ItemTemplate>
                        <asp:Button ID="cmdSel" runat="server" Text="View" />
                    </ItemTemplate>
                </asp:TemplateField> 
                </Columns>
        </asp:GridView>

Code to load is this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadGrid();
    }

    void LoadGrid()
    {
        string strSQL =
            "SELECT ID, FirstName, LastName, HotelName, Description "  
            "FROM tblHotelsA ORDER BY HotelName";

        GridView1.DataSource = MyRst(strSQL);
        GridView1.DataBind();
    }

    public DataTable MyRst(string strSQL)
    {
        DataTable rstData = new DataTable();
        using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
        {
            using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
            {
                cmdSQL.Connection.Open();
                rstData.Load(cmdSQL.ExecuteReader());
            }
        }
        return rstData;
    }

And we now have this:

enter image description here

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:

enter image description here

So, choose create event - don't seem like happens, but you now have this:

                <asp:TemplateField HeaderText="View" ItemStyle-HorizontalAlign="Center">
                    <ItemTemplate>
                        <asp:Button ID="cmdSel" runat="server" Text="View"
                            onclick="cmdSel_Click"
                            />
                    </ItemTemplate>
                </asp:TemplateField> 

And flip to code behind, and we can write this code:

    protected void cmdSel_Click(object sender, EventArgs e)
    {
        Button btnSel = sender as Button;
        GridViewRow gRow = btnSel.NamingContainer as GridViewRow;

        Debug.Print("Row index click = "   gRow.RowIndex.ToString());

        // get database PK id of this row

        int? PKID = GridView1.DataKeys[gRow.RowIndex]["ID"] as int?;

        Debug.Print("Database PK row id = "   PKID.ToString());

        // Get first name - databound - so we use cells[] array

        Debug.Print("First name = "   gRow.Cells[0].Text);

        // get a templated column - hotel label

        Label lblHotel = gRow.FindControl("txtHotel") as Label;
        Debug.Print("Hotel name = "   lblHotel.Text);
    }

output:

enter image description here

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:

    <div id ="OneHotel" runat="server" style="display:normal">

    <asp:Repeater ID="Repeater1" runat="server">
        <ItemTemplate>
            <div style="border-style:solid;color:black;width:400px;float:left;padding:8px">
            <div style="padding:5px;text-align:right">
            <p>Hotel Name: <asp:TextBox ID="HotelName" runat="server" Text ='<%# Eval("HotelName") %>' /></p>
            <p>First Name: <asp:TextBox ID="FirstName" runat="server" Text ='<%# Eval("FirstName") %>' /></p>
            <p>Last Name: <asp:TextBox ID="LastName" runat="server" Text ='<%# Eval("LastName") %>'    /></p>
            <p>City: <asp:TextBox ID="City" runat="server" Text ='<%# Eval("City") %>'  /></p>
            <p>Province: <asp:TextBox ID="Province" runat="server" Text ='<%# Eval("Province") %>'  /></p>
            Active: <asp:CheckBox ID="Active" runat="server" Checked = '<%# Eval("Active") %>'/>
            </div>
            <p>Description:
                <asp:TextBox ID="Description" runat="server" Text ='<%# Eval("Description") %>'
                    TextMode="MultiLine" rows="6" Columns="40" /></p>

            <asp:Button ID="cmdSave" runat="server" Text="Save" CssClass="btn"
                style="float:left"
                />
            <asp:Button ID="cmdCancel" runat="server" Text="Cancel" CssClass="btn"
                style="float:right"/>
        </div>
        </ItemTemplate>
    </asp:Repeater>
    </div>

Then in my gV button click above, we could add this code:

        OneHotel.Style.Add("display", "normal");
        GridView1.Style.Add("display", "none");

        Repeater1.DataSource = MyRst("SELECT * from tblHotelsA where id = "   PKID);
        Repeater1.DataBind();

Now, when I click on a row, I get this:

enter image description here

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.

  • Related