在 GridView 的 DropDownList 中动态创建的项目不起作用
Dynamically created Items in DropDownList in GridView not working
我在 .aspx
页面上有一个 .ascx
用户控件。当用户单击按钮时,信息将被收集并存储在数据库中。然后 Gridview
是数据绑定的,在 Gridview
中是 dropdownlist
。这个 dropdownlist
的 Items
是根据用户以前的输入动态创建的,现在在数据库中。这一切都很好,Gridview
与 dropdownlist
一起显示,动态创建 Items
。
问题出在这个 dropdownlist
的回发上。我显然必须用 dropdownlist
中动态创建的 Items
重新创建 Gridview
。这是行不通的。回发发生并调用 Page_Init
,然后调用 Page_InitComplete
。这在调用 SectionGV_OnRowDataBound
方法的 Gridview
上有数据绑定。 dropdownlists
已重新创建。但是 SectionDD_OnSelectedIndexChanged
方法永远不会被命中,然后 dropdownlist
就会恢复到它的原始值。我无法更改 dropdownlist's
所选值。
.ASCX
<asp:GridView ID="MyGV" runat="server" AutoGenerateColumns="False" DataSourceID="MyDS" Width="100%" OnRowDataBound="MyGV_OnRowDataBound">
<Columns>
<asp:TemplateField >
<ItemTemplate>
<asp:DropDownList runat="server" ID="SectionDD" AppendDataBoundItems="True" AutoPostBack="True" OnSelectedIndexChanged="SectionDD_OnSelectedIndexChanged" >
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
.ASCX 的隐藏代码
protected void Page_Init(object sender,EventArgs e)
{
this.Page.InitComplete += Page_InitComplete;
}
private void Page_InitComplete(object sender, EventArgs e)
{
MyGV.DataBind();
}
protected void SectionDD_OnSelectedIndexChanged(object sender, EventArgs e)
{
}
protected void MyGV_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
using (EntitiesModel dbContext = new EntitiesModel())
{
// get array[][] array from database
if (e.Row.RowType == DataControlRowType.DataRow)
{
DropDownList sectionDd = (DropDownList)e.Row.FindControl("SectionDD");
sectionDd.Items.Clear();
if (array.Length == 3)
{
if(array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
if(array[1][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[1][0], array[1][1]));
if(array[2][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[2][0], array[2][1]));
}
else if (array.Length == 2)
{
if (array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
if (array[1][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[1][0], array[1][1]));
}
else if (array.Length == 1)
{
if (array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
}
}
sectionDd.DataBind();
}
}
}
}
private void SaveToDB()
{
//save information to database
注意
这是我尝试解决此问题的众多方法之一。我将信息保存在数据库中的原因只是临时修复。我只想解决上面概述的问题,然后我将添加一个 ViewState
解决方案。
首先,不,您不必重新创建或再次加载网格,或 post 后面的下拉列表。
用户可以在网格中键入 - 更改值,select 下拉列表。那时你可以循环所有的网格行和 get/grab 下拉值 selected.
您当然可以 fire/trigger 下拉菜单的事件,但如果您真的需要该事件,则完全不清楚。
如果您的网格在 post-backs 上变得混乱?,那么这意味着您没有在第一个页面加载时限制负载 - 之后,它应该无关紧要。
假设我们有这个网格标记:
<asp:GridView ID="MyGrid" runat="server" CssClass="table table-hover"
DataKeyNames="ID" AutoGenerateColumns="false" OnRowDataBound="MyGrid_RowDataBound" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
<asp:BoundField DataField="HotelName" HeaderText="Hotel Name" />
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:DropDownList ID="DropDownList1" runat="server"
DataTextField="City"
DataValueField="City"
>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Province" HeaderText="Province" />
</Columns>
</asp:GridView>
当然还有非模板化字段(例如前几个 boundField - 它们出现在 .Cells 集合中。但是对于模板化列,您可以使用 findcontrol。
但是,在每一个和几乎所有的网页中,作为一般规则,您只能加载+混乱+创建网格一次,并且您只能在第一页加载时执行此操作 - 故事结束!你不遵守这个规则,那你就处在一个受伤的世界里。
好的,让我们加载网格。由于我们有一个下拉列表,因此我们必须在项目数据绑定上填写它。
因此,我们的代码将如下所示:
public DataTable rstCity = new DataTable();
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
{
LoadGrid();
}
}
public void LoadGrid()
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT City from City Order by City",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
// locd city for drop down list
cmdSQL.Connection.Open();
rstCity.Load(cmdSQL.ExecuteReader());
// now load grid
cmdSQL.CommandText = "SELECT * from tblHotels ORDER BY HotelName";
DataTable rst = new DataTable();
rst.Load(cmdSQL.ExecuteReader());
MyGrid.DataSource = rst;
MyGrid.DataBind();
}
}
请注意我是如何将城市 table 的范围缩小到 class 级别(没有理由为每一行一遍又一遍地加载它 - 在第一页加载后,它会消失范围 - 我们不关心 - 它会在第一个页面加载和数据绑定期间存在。
好的,现在,让我们为网格绑定项目数据。
我们有这个:
protected void MyGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
DropDownList MyDrop = (DropDownList)e.Row.FindControl("DropDownList1");
MyDrop.DataSource = rstCity;
MyDrop.DataBind();
// get City from current data row source - set the drop to data row City
MyDrop.SelectedValue = ((DataRowView)e.Row.DataItem)["City"].ToString();
}
}
好的,就是这样。 (请注意“数据项”如何仅在数据绑定期间存在 - 一个方便的提示,从那时起您就可以使用完整的数据行 - 包括 PK 值,以及您甚至不包含在网格标记中的列。但是,一旦数据绑定结束,则无法使用 DataItem - 它仅在绑定过程中存在。但此信息非常有价值,因为您可以充分利用用于绑定的实际数据源。在上面,我需要城市该行设置下拉列表。
好的,输出现在看起来像这样:
好的,此时我们只在第一页加载时绑定。该网格的视图状态由 asp.net 此时为您 100% 自动处理。
您可以在此表单上放置其他按钮 - post 背面应该无关紧要。这里的重要教训是 gridview 确实存在(至少如果我们不在 post-back 上每次都重新加载它,它会持续存在 - 你不应该重新加载)。
好的,下一期:
在大多数情况下,我认为网格中不需要下拉列表事件?
因此,作为一般规则,您可以 select 并更改任何行组合。完成后,您可以通过循环数据网格行来获取每一行的值 - 包括下拉列表的值 selected.
但是,让我们连接下拉列表事件。
每日提示:
由于我们不能 select 下拉菜单并使用 属性 sheet?
然后在标记中执行此操作:
您可以在 OnSelectedIndexChanged=
中的标记类型
当您点击“=”符号时,请注意非常接近 intel-sense 弹出事件创建选项的方式:
因此,单击 CreateNewEvent - 它“似乎”什么也没发生,但实际上,如果您转到代码隐藏,就会创建一个不错的事件存根。
所以让我们把我们的代码放在那个事件中 - 抓取行 - 显示刚刚选择的值。
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
// user changed the combo box
DropDownList MyDrop = (DropDownList)sender;
GridViewRow gRow = (GridViewRow)MyDrop.Parent.Parent;
Response.Write("<h2>Row index is = " + gRow.RowIndex.ToString() + "</h2>");
Response.Write("<h2>Hotel Name is = " + gRow.Cells[2].Text + "</h2>");
Response.Write("<h2>City from Drop selected = " + MyDrop.SelectedValue + "</h2>");
}
当然我们也在下拉标记中设置了 auto post back = true - 对吗?
并注意我们如何获取“发送者”,然后使用 .Parent.Parent 获取我们正在操作的网格行。第一个父级是某个单元格或类似单元格。
我实际上构建了递归函数来获取该值,但是在这里我们只使用了.parent.Parent(通常带有额外的标记,然后您需要再去操作一个.parent)。
反正现在说我select上面最后一个下拉,改一下
我得到这个输出:
总结:
永远只加载网格 - 第一页加载 -(你检查 IsPostBack)。
您可以拥有各种额外的 post 后盾 - 网格应该可以生存并且很好 - 根本不需要重新加载。 (GridView 已内置视图状态)。
现在,在我玩得开心之后,更改多行的下拉列表?
我可以循环显示网格视图 - 对该网格的任何更改都将保留。
事实上,如果您将列设置为文本框(在模板化字段中),那么您几乎可以像 Excel 一样进行 Tab 和编辑,并且这些值将再次为您保留,并且它们会继续存在 post 支持。 (然后您可以在一个更新命令中将整个更改网格发送回数据库 - 我可以展示如何执行此操作,但是这个 post 已经有很多好东西了。
现在,说了以上,做了以上?
好吧,现在我们有了这个工作,然后可以回去构建自定义用户控件 - 但如果做得好,它也应该正确运行,并且还应该在 post-backs 中存活下来。
我在 .aspx
页面上有一个 .ascx
用户控件。当用户单击按钮时,信息将被收集并存储在数据库中。然后 Gridview
是数据绑定的,在 Gridview
中是 dropdownlist
。这个 dropdownlist
的 Items
是根据用户以前的输入动态创建的,现在在数据库中。这一切都很好,Gridview
与 dropdownlist
一起显示,动态创建 Items
。
问题出在这个 dropdownlist
的回发上。我显然必须用 dropdownlist
中动态创建的 Items
重新创建 Gridview
。这是行不通的。回发发生并调用 Page_Init
,然后调用 Page_InitComplete
。这在调用 SectionGV_OnRowDataBound
方法的 Gridview
上有数据绑定。 dropdownlists
已重新创建。但是 SectionDD_OnSelectedIndexChanged
方法永远不会被命中,然后 dropdownlist
就会恢复到它的原始值。我无法更改 dropdownlist's
所选值。
.ASCX
<asp:GridView ID="MyGV" runat="server" AutoGenerateColumns="False" DataSourceID="MyDS" Width="100%" OnRowDataBound="MyGV_OnRowDataBound">
<Columns>
<asp:TemplateField >
<ItemTemplate>
<asp:DropDownList runat="server" ID="SectionDD" AppendDataBoundItems="True" AutoPostBack="True" OnSelectedIndexChanged="SectionDD_OnSelectedIndexChanged" >
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
.ASCX 的隐藏代码
protected void Page_Init(object sender,EventArgs e)
{
this.Page.InitComplete += Page_InitComplete;
}
private void Page_InitComplete(object sender, EventArgs e)
{
MyGV.DataBind();
}
protected void SectionDD_OnSelectedIndexChanged(object sender, EventArgs e)
{
}
protected void MyGV_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
using (EntitiesModel dbContext = new EntitiesModel())
{
// get array[][] array from database
if (e.Row.RowType == DataControlRowType.DataRow)
{
DropDownList sectionDd = (DropDownList)e.Row.FindControl("SectionDD");
sectionDd.Items.Clear();
if (array.Length == 3)
{
if(array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
if(array[1][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[1][0], array[1][1]));
if(array[2][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[2][0], array[2][1]));
}
else if (array.Length == 2)
{
if (array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
if (array[1][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[1][0], array[1][1]));
}
else if (array.Length == 1)
{
if (array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
}
}
sectionDd.DataBind();
}
}
}
}
private void SaveToDB()
{
//save information to database
注意
这是我尝试解决此问题的众多方法之一。我将信息保存在数据库中的原因只是临时修复。我只想解决上面概述的问题,然后我将添加一个 ViewState
解决方案。
首先,不,您不必重新创建或再次加载网格,或 post 后面的下拉列表。
用户可以在网格中键入 - 更改值,select 下拉列表。那时你可以循环所有的网格行和 get/grab 下拉值 selected.
您当然可以 fire/trigger 下拉菜单的事件,但如果您真的需要该事件,则完全不清楚。
如果您的网格在 post-backs 上变得混乱?,那么这意味着您没有在第一个页面加载时限制负载 - 之后,它应该无关紧要。
假设我们有这个网格标记:
<asp:GridView ID="MyGrid" runat="server" CssClass="table table-hover"
DataKeyNames="ID" AutoGenerateColumns="false" OnRowDataBound="MyGrid_RowDataBound" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
<asp:BoundField DataField="HotelName" HeaderText="Hotel Name" />
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:DropDownList ID="DropDownList1" runat="server"
DataTextField="City"
DataValueField="City"
>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Province" HeaderText="Province" />
</Columns>
</asp:GridView>
当然还有非模板化字段(例如前几个 boundField - 它们出现在 .Cells 集合中。但是对于模板化列,您可以使用 findcontrol。
但是,在每一个和几乎所有的网页中,作为一般规则,您只能加载+混乱+创建网格一次,并且您只能在第一页加载时执行此操作 - 故事结束!你不遵守这个规则,那你就处在一个受伤的世界里。
好的,让我们加载网格。由于我们有一个下拉列表,因此我们必须在项目数据绑定上填写它。
因此,我们的代码将如下所示:
public DataTable rstCity = new DataTable();
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
{
LoadGrid();
}
}
public void LoadGrid()
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT City from City Order by City",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
// locd city for drop down list
cmdSQL.Connection.Open();
rstCity.Load(cmdSQL.ExecuteReader());
// now load grid
cmdSQL.CommandText = "SELECT * from tblHotels ORDER BY HotelName";
DataTable rst = new DataTable();
rst.Load(cmdSQL.ExecuteReader());
MyGrid.DataSource = rst;
MyGrid.DataBind();
}
}
请注意我是如何将城市 table 的范围缩小到 class 级别(没有理由为每一行一遍又一遍地加载它 - 在第一页加载后,它会消失范围 - 我们不关心 - 它会在第一个页面加载和数据绑定期间存在。
好的,现在,让我们为网格绑定项目数据。
我们有这个:
protected void MyGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
DropDownList MyDrop = (DropDownList)e.Row.FindControl("DropDownList1");
MyDrop.DataSource = rstCity;
MyDrop.DataBind();
// get City from current data row source - set the drop to data row City
MyDrop.SelectedValue = ((DataRowView)e.Row.DataItem)["City"].ToString();
}
}
好的,就是这样。 (请注意“数据项”如何仅在数据绑定期间存在 - 一个方便的提示,从那时起您就可以使用完整的数据行 - 包括 PK 值,以及您甚至不包含在网格标记中的列。但是,一旦数据绑定结束,则无法使用 DataItem - 它仅在绑定过程中存在。但此信息非常有价值,因为您可以充分利用用于绑定的实际数据源。在上面,我需要城市该行设置下拉列表。
好的,输出现在看起来像这样:
好的,此时我们只在第一页加载时绑定。该网格的视图状态由 asp.net 此时为您 100% 自动处理。
您可以在此表单上放置其他按钮 - post 背面应该无关紧要。这里的重要教训是 gridview 确实存在(至少如果我们不在 post-back 上每次都重新加载它,它会持续存在 - 你不应该重新加载)。
好的,下一期:
在大多数情况下,我认为网格中不需要下拉列表事件?
因此,作为一般规则,您可以 select 并更改任何行组合。完成后,您可以通过循环数据网格行来获取每一行的值 - 包括下拉列表的值 selected.
但是,让我们连接下拉列表事件。
每日提示: 由于我们不能 select 下拉菜单并使用 属性 sheet?
然后在标记中执行此操作:
您可以在 OnSelectedIndexChanged=
中的标记类型当您点击“=”符号时,请注意非常接近 intel-sense 弹出事件创建选项的方式:
因此,单击 CreateNewEvent - 它“似乎”什么也没发生,但实际上,如果您转到代码隐藏,就会创建一个不错的事件存根。
所以让我们把我们的代码放在那个事件中 - 抓取行 - 显示刚刚选择的值。
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
// user changed the combo box
DropDownList MyDrop = (DropDownList)sender;
GridViewRow gRow = (GridViewRow)MyDrop.Parent.Parent;
Response.Write("<h2>Row index is = " + gRow.RowIndex.ToString() + "</h2>");
Response.Write("<h2>Hotel Name is = " + gRow.Cells[2].Text + "</h2>");
Response.Write("<h2>City from Drop selected = " + MyDrop.SelectedValue + "</h2>");
}
当然我们也在下拉标记中设置了 auto post back = true - 对吗?
并注意我们如何获取“发送者”,然后使用 .Parent.Parent 获取我们正在操作的网格行。第一个父级是某个单元格或类似单元格。
我实际上构建了递归函数来获取该值,但是在这里我们只使用了.parent.Parent(通常带有额外的标记,然后您需要再去操作一个.parent)。
反正现在说我select上面最后一个下拉,改一下
我得到这个输出:
总结:
永远只加载网格 - 第一页加载 -(你检查 IsPostBack)。
您可以拥有各种额外的 post 后盾 - 网格应该可以生存并且很好 - 根本不需要重新加载。 (GridView 已内置视图状态)。
现在,在我玩得开心之后,更改多行的下拉列表?
我可以循环显示网格视图 - 对该网格的任何更改都将保留。
事实上,如果您将列设置为文本框(在模板化字段中),那么您几乎可以像 Excel 一样进行 Tab 和编辑,并且这些值将再次为您保留,并且它们会继续存在 post 支持。 (然后您可以在一个更新命令中将整个更改网格发送回数据库 - 我可以展示如何执行此操作,但是这个 post 已经有很多好东西了。
现在,说了以上,做了以上?
好吧,现在我们有了这个工作,然后可以回去构建自定义用户控件 - 但如果做得好,它也应该正确运行,并且还应该在 post-backs 中存活下来。