ASP.NET GridView行按钮触发模态框加载Highcharts数据异常
解决GridView行按钮打开模态框显示最后一行数据的问题
为什么会出现这个问题?
你碰到的这个坑其实很典型,核心原因有两个:
- 共用单一资源导致覆盖:所有行的按钮都绑定同一个模态框,而且你在
RowDataBound里循环给隐藏变量赋值时,后面行的赋值会直接覆盖前面的,最后就只剩最后一行的数据留在隐藏变量里了。 - Highcharts容器重复绑定:固定ID的
#container容器每次调用JS方法都会被重新初始化,最后一次渲染的图表会把之前的完全覆盖掉。
一步步修复方案
1. 调整GridView按钮与模态框结构
首先给每个按钮带上当前行的MeasureID,让点击时能精准识别要加载的数据;同时调整模态框,方便动态更新标题和图表:
GridView模板列修改:
<asp:TemplateField HeaderText="AvgDaysTaken" SortExpression="AvgDaysTaken"> <ItemTemplate> <asp:Button type="button" runat="server" id="AvgDaysTaken" class="btn btn-primary boxplot-btn" data-toggle="modal" data-target="#myBoxPlot" data-measureid='<%# Eval("MeasureID") %>' Text='<%# Eval("AvgDaysTaken") %>' /> </ItemTemplate> </asp:TemplateField>
这里加了data-measureid属性存当前行的MeasureID,还给按钮加了自定义类方便后续绑定事件。
模态框修改:
<div class="container"> <div class="modal fade" id="myBoxPlot" role="dialog"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal">×</button> <h4 class="modal-title" id="plotTitle"></h4> </div> <div class="modal-body"> <div id="chartContainer"></div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> </div> </div> </div> </div> </div>
新增了plotTitle用于动态显示图表标题,图表容器改成chartContainer,方便每次清空重绘。
2. 后端改成Web方法,按需返回数据
不用再提前给隐藏变量赋值了,改成通过AJAX调用的Web方法,需要哪个ID的数据就请求哪个:
[System.Web.Services.WebMethod] public static string GetBoxPlotData(int measureid, string subTeam, int month, int year, string reportPeriod, int controlID, int practiceGroup, string instructor) { string connectionstring = System.Configuration.ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString; // 使用using确保资源自动释放 using (SqlConnection cn = new SqlConnection(connectionstring)) { using (SqlCommand cmd = new SqlCommand("[dbo].so", cn)) { cmd.CommandType = CommandType.StoredProcedure; // 传入所有需要的参数 cmd.Parameters.AddWithValue("@SubTeamNo", subTeam); cmd.Parameters.AddWithValue("@Month", month); cmd.Parameters.AddWithValue("@Year", year); cmd.Parameters.AddWithValue("@ReportPeriod", reportPeriod); cmd.Parameters.AddWithValue("@controlID", controlID); cmd.Parameters.AddWithValue("@PracticeGroupID", practiceGroup); cmd.Parameters.AddWithValue("@Instructor", instructor); cmd.Parameters.AddWithValue("@MeasureID", measureid); cn.Open(); using (SqlDataReader reader = cmd.ExecuteReader()) { if (reader.HasRows && reader.Read()) { string measureDesc = reader["MeasureDesc"].ToString(); string chartDataStr = reader["ChartData"].ToString(); // 把字符串数组转成数值数组 int[] numericData = Array.ConvertAll(chartDataStr.Split(','), int.Parse); // 返回包含描述和数据的JSON return Newtonsoft.Json.JsonConvert.SerializeObject(new { MeasureDesc = measureDesc, ChartData = numericData }); } // 没有数据时返回默认值 return Newtonsoft.Json.JsonConvert.SerializeObject(new { MeasureDesc = "No Data Available", ChartData = new int[0] }); } } } }
注意这里要把页面上的SubTeam、Month等变量传递进去,确保查询条件正确。
3. 前端JS:绑定模态框事件,动态加载渲染图表
现在让模态框在打开时才去请求对应ID的数据,然后渲染图表:
// 监听模态框打开事件,获取数据并渲染图表 $('#myBoxPlot').on('show.bs.modal', function (event) { const button = $(event.relatedTarget); // 点击的那个按钮 const measureID = button.data('measureid'); // 拿到当前行的MeasureID const modal = $(this); const titleEl = modal.find('#plotTitle'); const chartContainer = modal.find('#chartContainer'); // 先清空之前的图表内容,避免残留 chartContainer.empty(); // AJAX调用后端Web方法 $.ajax({ type: "POST", url: "YourPageName.aspx/GetBoxPlotData", // 替换成你的页面文件名 data: JSON.stringify({ measureid: measureID, subTeam: '<%= SubTeam %>', month: <%= Month %>, year: <%= Year %>, reportPeriod: '<%= ReportPeriod %>', controlID: <%= ControlID %>, practiceGroup: <%= PracticeGroup %>, instructor: '<%= SearchInput.Text %>' }), contentType: "application/json; charset=utf-8", dataType: "json", success: function (response) { const data = response.d; // ASP.NET Web方法返回的结果在d属性里 titleEl.text(data.MeasureDesc); // 渲染Highcharts箱线图 Highcharts.chart(chartContainer[0], { chart: { type: 'boxplot', inverted: true }, title: { text: data.MeasureDesc }, subtitle: { text: 'Test Chart Subtitle' }, legend: { enabled: false }, tooltip: { shared: true, crosshairs: true }, plotOptions: { series: { pointWidth: 50 } }, xAxis: { visible: false }, yAxis: { visible: true, title: { text: 'Values' }, plotLines: [{ value: 80, color: 'red', width: 2 }] }, series: [{ data: [data.ChartData] }] }); }, error: function (xhr, status, error) { console.error('加载数据失败:', error); titleEl.text('数据加载失败,请重试'); } }); }); // 模态框关闭时销毁图表,释放内存 $('#myBoxPlot').on('hidden.bs.modal', function () { const chartContainer = $(this).find('#chartContainer')[0]; // 找到对应的Highcharts实例并销毁 const chart = Highcharts.charts.find(c => c.renderTo === chartContainer); if (chart) chart.destroy(); });
这里的关键是:
- 每次打开模态框才请求对应ID的数据,彻底避免了数据覆盖
- 动态渲染图表,每次都清空容器并创建新实例,解决了容器ID冲突
- 关闭模态框时销毁图表,避免内存泄漏
为什么这个方案能解决问题?
- 取消了提前赋值隐藏变量的逻辑,改成按需获取数据,再也不会有覆盖问题
- 每个按钮都携带自己的
MeasureID,点击时能精准定位要加载的数据 - Highcharts每次都在干净的容器里创建新图表,不会和之前的实例冲突
内容的提问来源于stack exchange,提问作者RA19
相关产品推荐
相关产品推荐

