如何在代码隐藏中更新隐藏字段的值
How to update a hidden field's value in code behind
在 Albert 关于 GridView
s 的出色回答之后,我清楚地知道问题的原始措辞导致了混淆。 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
而不是 ViewState
。 ViewState
无法在页面及其控件之间共享。 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();
在 Albert 关于 GridView
s 的出色回答之后,我清楚地知道问题的原始措辞导致了混淆。 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
而不是 ViewState
。 ViewState
无法在页面及其控件之间共享。 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();