WinForms MonthCalendar二次打开卡顿问题求助
我开发了一个基础WinForms弹窗,点击主窗体按钮即可弹出,仅包含MonthCalendar控件和确定/取消按钮。首次打开弹窗时,控件响应流畅,可正常切换月份、选择日期,日期变量更新及返回逻辑均正常;但点击确定关闭弹窗后再次打开,会出现严重卡顿:月份切换箭头无响应、鼠标悬停时箭头不高亮、鼠标点击选中的日期位置偏移(甚至无选中)等异常。
弹窗(CalendarForm)代码
public partial class CalendarForm : Form { private DateTime selectedDate; private bool isDateSelected; public CalendarForm(DateTime initialDate) { InitializeComponent(); InitializeCustomComponents(initialDate); } private void InitializeCustomComponents(DateTime initialDate) { this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.MinimizeBox = false; CultureInfo ci = new CultureInfo("es-ES"); monthCalendar.TitleBackColor = System.Drawing.Color.LightBlue; monthCalendar.TitleForeColor = System.Drawing.Color.White; monthCalendar.TrailingForeColor = System.Drawing.Color.Gray; monthCalendar.FirstDayOfWeek = Day.Monday; monthCalendar.CalendarDimensions = new System.Drawing.Size(1, 1); monthCalendar.TodayDate = initialDate; monthCalendar.SelectionStart = initialDate; monthCalendar.SelectionEnd = initialDate; btnOK.Click += BtnOK_Click; btnCancel.Click += BtnCancel_Click; } private void BtnOK_Click(object sender, EventArgs e) { selectedDate = monthCalendar.SelectionStart; isDateSelected = true; this.DialogResult = DialogResult.OK; } private void BtnCancel_Click(object sender, EventArgs e) { isDateSelected = false; this.DialogResult = DialogResult.Cancel; } public (DateTime, bool) GetSelectedDate() { return (selectedDate, isDateSelected); } }
弹窗调用逻辑
private async void btnCalendar_Click(object sender, EventArgs e) { bool updated = false; using (CalendarForm calendarForm = new CalendarForm(selectedDate)) { if (calendarForm.ShowDialog() == DialogResult.OK) { (DateTime date, bool isDateSelected) = calendarForm.GetSelectedDate(); if (isDateSelected) { selectedDate = date; updated = true; } } } if (updated) { await LoadDataForDate(selectedDate); } }
补充:LoadDataForDate 代码
private async Task LoadDataForDate(DateTime date) { string dateString = date.ToString("yyyyMMdd"); basketLayers = await Task.Run(() => Backend.GetHeatData(dateString, globalversion, 0)); DataTable heatBasketTable = CreateHeatBasketDataTable(basketLayers); dataGridViewHeatBaskets.DataSource = heatBasketTable; DataTable basketLayerTable = CreateBasketLayerDataTable(basketLayers); dataGridViewBasketLayers.DataSource = basketLayerTable; SetColumnHeaders(1); lblSelectedDate.Text = date.ToString("yyyy-MM-dd"); MakeHeadersNonClickable(dataGridViewBasketLayers); MakeHeadersNonClickable(dataGridViewHeatBaskets); dataGridViewBasketLayers.DataBindingComplete += DataGridViewBasketLayers_DataBindingComplete; }
补充:GetHeatData 代码(含伪代码)
public static async Task<List<BasketLayer>> GetHeatData(string date) { List<BasketLayer> basketLayers = new List<BasketLayer>(); using (SqlConnection localcnn = new SqlConnection(_connetionString)) { await localcnn.OpenAsync(); StringBuilder sb = new StringBuilder(); sb.AppendLine("some stuff, using the date argument"); SqlCommand comando = new SqlCommand(sb.ToString(), localcnn); SqlDataReader reader = await comando.ExecuteReaderAsync(); while (await reader.ReadAsync()) { int someVariables = int.Parse(reader.GetString(reader.GetOrdinal("SQLvariables"))); BasketLayer basketLayer = new BasketLayer(someFields); basketLayers.Add(basketLayer); } reader.Close(); } Dictionary<int, int> basketOffsetDict = new Dictionary<int, int>(); foreach (var basketLayer in basketLayers) { //a small routine to update a class field basketOffset = CheckForOlderBaskets(somevariables); //this should be async because it's an SQL I/O basketLayer.Basket += basketOffset; } return basketLayers; }
补充:DataBindingComplete 事件取消订阅代码
private void DataGridViewBasketLayers_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) { ReadOnlyData(); dataGridViewBasketLayers.DataBindingComplete -= DataGridViewBasketLayers_DataBindingComplete; }
补充:ReadOnlyData 代码
private void ReadOnlyData() { foreach (DataGridViewColumn column in dataGridViewHeatBaskets.Columns) { column.ReadOnly = true; } if (globalversion == "original") { foreach (DataGridViewColumn column in dataGridViewBasketLayers.Columns) { column.ReadOnly = true; } } else if (globalversion == "update") { dataGridViewBasketLayers.Columns["Col1"].ReadOnly = true; dataGridViewBasketLayers.Columns["Col2"].ReadOnly = true; dataGridViewBasketLayers.Columns["Col3"].ReadOnly = true; dataGridViewBasketLayers.Columns["Col4"].ReadOnly = true; foreach (DataGridViewRow row in dataGridViewBasketLayers.Rows) { if (!RowHasValues(row)) { row.Cells["Col5"].ReadOnly = true; } else { row.Cells["Col5"].ReadOnly = false; } } } }
核心问题:UI线程资源占用异常
二次打开弹窗卡顿的根源是UI线程消息循环被干扰,或线程池资源被长期占用,具体修复点如下:
DataGridView事件重复绑定隐患
每次调用LoadDataForDate都会绑定DataBindingComplete事件,虽然事件处理中会取消订阅,但如果某次绑定后事件未触发,订阅会累积,后续UI操作时多个回调抢占资源,间接影响弹窗控件响应。
修复:绑定前先移除旧订阅,确保每次只有一个订阅存在:// 先取消旧订阅,再绑定新的 dataGridViewBasketLayers.DataBindingComplete -= DataGridViewBasketLayers_DataBindingComplete; dataGridViewBasketLayers.DataBindingComplete += DataGridViewBasketLayers_DataBindingComplete;同步SQL操作阻塞线程池
CheckForOlderBaskets是同步SQL操作,放在Task.Run中会长时间占用线程池线程,导致WinForms控件的消息循环处理延迟(MonthCalendar对UI消息敏感度高)。
修复:将CheckForOlderBaskets改造为异步方法,用await调用:// 假设改造后的异步方法名为CheckForOlderBasketsAsync basketOffset = await CheckForOlderBasketsAsync(somevariables); basketLayer.Basket += basketOffset;MonthCalendar文化信息未应用
代码中创建了CultureInfo但未绑定到控件,可能导致控件内部渲染计算异常,二次打开时触发卡顿。
修复:将文化信息应用到控件:// 在InitializeCustomComponents方法中添加 monthCalendar.Culture = ci;弹窗关闭时事件未清理
虽然用using块释放弹窗,但控件绑定的事件可能未及时清理,残留的引用影响资源回收。
修复:在CalendarForm中添加关闭时的事件清理:// 在CalendarForm类中添加 private void CalendarForm_FormClosing(object sender, FormClosingEventArgs e) { btnOK.Click -= BtnOK_Click; btnCancel.Click -= BtnCancel_Click; } // 在InitializeCustomComponents中绑定该事件 this.FormClosing += CalendarForm_FormClosing;
验证步骤
- 先修复事件重复绑定问题,测试二次打开弹窗是否恢复正常;
- 若仍卡顿,再处理
CheckForOlderBaskets的异步改造; - 最后添加文化信息绑定和弹窗事件清理,保障长期运行稳定性。
内容的提问来源于stack exchange,提问作者ATSlooking4things

