如何在代码隐藏中更新隐藏字段的值

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

在 Albert 关于 GridViews 的出色回答之后,我清楚地知道问题的原始措辞导致了混淆。 GridView 只是控件上的一个元素 - 对于所有范围和目的,它可能只是一个按钮,其值存储在数据标签中。

为了清楚起见,我将 post 页面和内部控件的完整标记,以及当前形式的完整代码。

这是 ascx 标记:

<%@ 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>

现在我可以从 UpdatePanel 中提取 GridView,但我不确定这会有什么不同。

在“ASCX”的隐藏代码中,我有这个:

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);
        }
    }
}

现在,我知道这不一定是最好的方法 - 但它是我们维护的遗留系统,同时在 Blazor 中编写新系统以最终取代它。所以我不一定要寻找最好的方法,只是寻找一种可行的方法——我已经从系统的其他部分提取了一半的代码,将其放入一个集中的页面中。

现在父 ASPX 目前看起来像这样:

<%@ 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>

后面的代码非常简单:

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();
    }
}

现在,ASCX 中的所有内容似乎都运行良好 - 我的问题是,在单步执行代码时,我可以看到正在更新的值,正如我所期望的那样;但是 Chrome 上的开发工具 window 中的最终产品表明,虽然 hfTEST(我放入 ascx 中以测试该机制的隐藏字段)值正在更新, hfTGisn 的值不是。正如这个截屏所示:

我不太在意如何将值放入父 ASPX(此时,尽管它给我带来了所有麻烦,但我正在争论是否将其全部从 ASCX 中提取出来,只是将其全部推入 ASPX)- 我只需要这个值,这样我就可以开始编写页面的其余部分了。

那么,从 ascx 中提取值到父页面的最简单方法是什么?


进一步尝试更新:

根据另一个答案的建议,我尝试利用 ViewState,将 ASPX 页面后面的代码更改为:

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;
    }
}

并将 gvTutorGroups_SelectedIndexChanged 方法更改为:

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 = "";
        }
    }
}

但是,ViewState 似乎没有填充在 Page_Load

在多个回发的情况下,您可以使用 ViewState.

因为 web 应用程序是无状态的,所以在请求整个页面及其控件后将再次创建,而前一个页面、控件及其值将丢失。 ViewState 帮助管理页面的状态。

您可以尝试这样的操作:

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

在后面的代码中添加 HfTGisn 作为 ProgCoachChangeRequester 部分 class 属性.

Page_Load中添加

hfTGisn.Value = HfTGisn;

gvTutorGroups_SelectedIndexChanged事件处理程序中添加

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

正如 Poul Bak 所述,您已经可以访问 ascx 控件 hfTGisn,我认为它是所有其他控件都相同,包括 hfTTGPISN.

这样从GridView中选择的ID保存在HfTGisn属性中(本质上是保存在ViewState中)因此不会丢失。 HfTGisn 属性 的值然后作为 hfTGisn 隐藏字段的值存储在 gvTutorGroups_SelectedIndexChanged 事件处理程序和 Page_Load.

你说过 hfTGisn 隐藏字段的值是在 Page_Load 中读取的,所以我想你可以直接从 属性 中读取它。更重要的是,你甚至可能不需要隐藏字段,如果它的唯一目的是存储从 GridView.

中选择的 ID

这是快速修复,但我认为更重要的是找到第二个回发的来源。除了在执行 gvTutorGroups_SelectedIndexChanged 之后调试每一步,我别无他法。

编辑:也许最简单的解决方案是从Page_Load中的GridView中读取选定的ID,并将其存储为隐藏字段的值. GridView 和其他控件默认将它们的值存储在 ViewState 中。

编辑 2: 谢谢你的澄清。

我没能重现这个问题,我已经成功地将值从控件传递到父项的隐藏字段。 但是,我认为有一种方法可以解决您面临的问题,虽然解决方案远非如此,但它有效(至少我希望它有效)。

应拒绝此答案中提出的所有其他更改。

我仍然怀疑隐藏字段的值在一些额外请求后被重置。让我们使用 Session 而不是 ViewStateViewState 无法在页面及其控件之间共享。 Session 可以在单个会话期间通过整个应用程序共享。

Session 中的 GridView 的事件处理程序中存储 ID 并将其命名为唯一的(您可以将名称存储在可以从代码的任何部分轻松访问的地方).也不要忘记将值存储到隐藏字段:

protected void gvTutorGroups_SelectedIndexChanged(object sender, EventArgs e)
{
    foreach (GridViewRow row in gvTutorGroups.Rows)
    {
        if (row.RowIndex == gvTutorGroups.SelectedIndex)
        {
            // ...
            
            Session["Utilities_CourseChange_MyClasses_gvTutorGroups_SelectedIndexChanged"] = ((HiddenField)row.FindControl("hfTTGPISN")).Value;
            
            HiddenField hfTGIsn = (HiddenField)this.Parent.FindControl("hfTGisn");
            hfTGIsn.Value = Session["Utilities_CourseChange_MyClasses_gvTutorGroups_SelectedIndexChanged"];
            
            // ...
        }
        else
        {
            row.CssClass = "";
        }
    }
}

在父页面的 Page_Load 中存储从会话到隐藏字段的值并且应该始终执行它,不仅在回发的情况下:

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)
    {
        hhfTGIsn.Value = Session["Utilities_CourseChange_MyClasses_gvTutorGroups_SelectedIndexChanged"];
    }
}

好的,这里有很多问题。我们将尝试解释一些:

首先,您没有显示网格视图标记。不是世界末日,但它确实增加了此页面的复杂性 - 虽然简单,但引入 GridView 确实引入了大量的学习曲线。

接下来: 您没有提及或注意您是如何触发 selected 索引的。也许你打开了这一行的“select”按钮?? (再次:这里有一个大细节)。

好的,所以无论如何,我们正在触发 selected 索引事件。

因此,考虑到这一点,您可以使用您的代码像这样获取当前网格视图行:

    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";

    }

因此,如果这是一个模板化列,您可以使用查找控件。

因此,如果这是一个绑定字段,那么您必须使用 .cells[] 数组。

现在,很多时候,我们想要单击一行,并传递数据库主键 ID,但我们肯定不想在 GV 中显示该键。

处理该问题的最佳方法是使用 DataKeys 功能。它简单、容易,而且很好而且安全,因为我们不必隐藏,甚至不必在网格标记中包含 PK 行。

那么,解决这个问题的方法是什么?通常我只是在标记中放入一个平面简常规 asp.net 按钮。

像这样说:

(对于演示,我将酒店名称作为标签 - 模板化列,因此我们有数据绑定字段的“混合”,模板化(标签),以及飞机简 asp.net按钮。

所以,我们有这个:

        <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>

要加载的代码是这样的:

    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;
    }

我们现在有这个:

所以,现在我们要做的就是为简单的平面简按钮单击事件添加代码存根。

当控件(和按钮)在 GV 之外时,您只需单击它们,显示 属性 sheet(甚至在设计视图中双击按钮,然后跳转到代码隐藏。

但是,您不能对 GV 内部的控件执行此操作,因此,我们必须转换为标记,并获取 Visual Studio 来连接该事件。

你简单的在onclick=中输入按钮,当你点击“=”时,弹出intel-sense,然后你可以选择创建点击事件。

你看到这个:

所以,选择创建事件 - 似乎没有发生,但你现在有这个:

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

然后翻到后面的代码,我们可以这样写代码:

    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);
    }

输出:

如前所述,我们不能在此处使用 DataItem 属性(以获取完整的基础数据行)。

DataItem 属性 仅在数据绑定事件期间有效。

然而,在大多数情况下,这应该无关紧要。

因为我们有网格视图行,所以我们可以显示任何值。

在我的示例中,我们可以在此处获取隐藏的数据库行PK id ("id")。同样,非常好,因为我们因此不必隐藏、尝试保存,甚至不必包含和显示数据库 PK id,但它由服务器端自动管理 - 无需保存或将该值推到某个地方,因为一旦我们有了 GV 行,我们就会得到行索引,并且有了行索引,我们就会得到基于该行索引的 datakeys 值。

所以,现在,我们可以跳转到另一个页面,甚至可以将 GV 隐藏在“div”中,然后显示 Repeater 项目或数据视图或其他任何内容。我们可能应该根据那个 PK id 来做。

所以,在同一页上,我放入了一个数据转发器。

(但隐藏),这样说:

    <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>

然后在我的gV按钮点击上面,我们可以添加这样的代码:

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

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

现在,当我点击一行时,我得到这个:

或者如前所述,我们可以跳转到另一页。

因此,一旦我们有了网格行,我们就可以从中得到我们想要的任何东西 - 甚至是 re-query 所有行数据来显示网格中不需要的列等。

所以,总结一下:

您可以放入飞机珍妮按钮 - 只需使用飞机珍妮点击事件。您可以使用“namingcontainer”来获取网格行。真的,可能比使用内置的 selected 索引事件更好更清晰。

您不能使用 DataItem 属性 在 databind() 发生后它将始终为 null。您可以在绑定期间的事件中和期间使用 DataItem(例如行数据绑定事件)。

您不需要显示、隐藏、隐藏行单击以获取标识该行的数据库 PK id - 这就是 GV 的数据键设置的用途。

编辑:考虑到以上几点?

好的,现在我们有了索引?

我们可以随意将该索引值“推”到隐藏控件中。

对于 GV 之外的控件,我们可以直接在代码中引用它们,不需要某种查找控件。

所以,我们会这样做:

        Button btnSel = sender as Button;
        GridViewRow gRow = btnSel.NamingContainer as GridViewRow;


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