两个DataGridView间移动行后,如何保持网格滚动位置不回到顶部?
问题描述
我有两个带按钮字段的DataGridView,要实现特定角色的功能授予与撤销操作:点击「授予」按钮时,对应功能从当前DataGridView移除并显示到另一个DataGridView;撤销操作逻辑相反。目前功能正常,但每次点击按钮都会重新从数据库绑定数据,导致网格回到顶部,想保持当前滚动位置,求解决思路。
后台代码
protected void grdAllFunctions_RowCommand(object sender, GridViewCommandEventArgs e) { int roleid = Convert.ToInt32(ddlUser.SelectedValue); int rowIndex = Convert.ToInt32(e.CommandArgument); GridViewRow row = grdAllFunctions.Rows[rowIndex]; int funcid = Convert.ToInt32(row.Cells[0].Text); UserClass obj = new UserClass(); try { if (ddlUser.SelectedValue == "0") { errorHandle(false, "Please Select the Role Name"); return; } else { DataSet ds = new DataSet(); ds.Merge(obj.checkgrantedfunctions(roleid, funcid)); if (ds.Tables[0].Rows.Count > 0) { errorHandle(false, "This Function is Already Assigned"); return; } else { obj.grant(roleid, funcid); errorHandle(true, "Successfully Granted"); DataSet dg = new DataSet(); dg.Merge(obj.ungrantedfunctions(roleid)); if (dg.Tables[0].Rows.Count > 0) { grdAllFunctions.DataSource = null; grdAllFunctions.DataSource = dg.Tables[0]; grdAllFunctions.DataBind(); } else { grdAllFunctions.DataSource = dg.Tables[0]; grdAllFunctions.DataBind(); } DataSet ds1 = new DataSet(); ds1.Merge(obj.grantedfunctions(roleid)); if (ds1.Tables[0].Rows.Count > 0) { grdGrantedFunctions.Visible = true; grdGrantedFunctions.DataSource = ds1.Tables[0]; grdGrantedFunctions.DataBind(); } return; } } } catch (Exception ex) { Response.Write("<script>alert('" + Server.HtmlEncode(ex.Message) + "')</script>"); return; } }
解决思路
方法1:保存并恢复滚动位置
在数据绑定前用JavaScript获取GridView容器的滚动位置,存入隐藏字段;绑定完成后再读取位置并恢复。
- 给GridView套一个带固定高度的滚动容器:
<div id="grdContainer" style="height:400px;overflow:auto;"> <asp:GridView ID="grdAllFunctions" runat="server" ...> <!-- 网格内容 --> </asp:GridView> </div>
- 添加隐藏字段存储滚动值:
<asp:HiddenField ID="hdnScrollTop" runat="server" />
- 按钮点击时先执行JS保存位置:
<asp:Button ID="btnGrant" runat="server" Text="授予" CommandName="Grant" OnClientClick="saveScrollPosition();" />
对应的JS函数:
function saveScrollPosition() { var container = document.getElementById('grdContainer'); document.getElementById('<%= hdnScrollTop.ClientID %>').value = container.scrollTop; }
- 数据绑定完成后注册脚本恢复位置:
ScriptManager.RegisterStartupScript(this, GetType(), "RestoreScroll", $"document.getElementById('grdContainer').scrollTop = {hdnScrollTop.Value};", true);
方法2:避免全量数据绑定(优化方案)
直接操作GridView的行,不用重新绑定整个数据集,从根源避免滚动丢失:
- 授予操作:从
grdAllFunctions移除目标行,克隆该行并替换按钮后添加到grdGrantedFunctions - 撤销操作:反向执行上述逻辑
- 同时仅更新数据库对应记录,不用拉取全量数据
示例代码(授予操作片段):
// 移除源网格的行 grdAllFunctions.Rows.RemoveAt(rowIndex); // 克隆行并替换按钮 GridViewRow newRow = row.Clone(); for (int i = 0; i < newRow.Cells.Count; i++) { newRow.Cells[i].Text = row.Cells[i].Text; if (i == newRow.Cells.Count - 1) { Button btnRevoke = new Button(); btnRevoke.Text = "撤销"; btnRevoke.CommandName = "Revoke"; btnRevoke.CommandArgument = grdGrantedFunctions.Rows.Count.ToString(); newRow.Cells[i].Controls.Add(btnRevoke); } } grdGrantedFunctions.Rows.Add(newRow); grdGrantedFunctions.Visible = true;
方法3:使用UpdatePanel局部刷新
把两个GridView放到UpdatePanel中,只刷新网格区域,页面整体不会回滚:
<asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:GridView ID="grdAllFunctions" runat="server" ...></asp:GridView> <asp:GridView ID="grdGrantedFunctions" runat="server" ...></asp:GridView> </ContentTemplate> <Triggers> <asp:AsyncPostBackTrigger ControlID="grdAllFunctions" EventName="RowCommand" /> <asp:AsyncPostBackTrigger ControlID="grdGrantedFunctions" EventName="RowCommand" /> </Triggers> </asp:UpdatePanel>
内容的提问来源于stack exchange,提问作者Aruna Prabhath
相关产品推荐
相关产品推荐

