.NET Framework 4.8(C#7.3)中DataGridView多单元格倒计时实现求助
.NET Framework 4.8 DataGridView多倒计时与文件读写实现方案
一、整体思路确认
你的思路完全可行:用CSV/TXT作为轻量存储,程序启动时加载数据到内存,运行时通过全局Timer统一更新所有行的倒计时状态,新增数据时同步写入文件。这种方案适合小规模数据场景,无需复杂数据库。
二、DataGridView列配置
先手动配置列(或代码初始化),对应需求的5列:
- 代码列:设为可编辑的
DataGridViewTextBoxColumn,允许用户输入 - 开始日期列:
DataGridViewTextBoxColumn,只读,格式设为yyyy-MM-dd HH:mm:ss - 结束日期列:
DataGridViewTextBoxColumn,只读,格式同上 - 倒计时列:
DataGridViewTextBoxColumn,只读,显示HH:mm:ss格式剩余时间 - 状态列:
DataGridViewTextBoxColumn,只读,显示“空闲/占用”
代码初始化示例(Form_Load事件中):
private void Form1_Load(object sender, EventArgs e) { // 初始化列 dataGridView1.Columns.AddRange(new DataGridViewColumn[] { new DataGridViewTextBoxColumn { Name = "Code", HeaderText = "代码", Width = 100 }, new DataGridViewTextBoxColumn { Name = "StartDate", HeaderText = "开始日期", Width = 150, ReadOnly = true, DefaultCellStyle = { Format = "yyyy-MM-dd HH:mm:ss" } }, new DataGridViewTextBoxColumn { Name = "EndDate", HeaderText = "结束日期", Width = 150, ReadOnly = true, DefaultCellStyle = { Format = "yyyy-MM-dd HH:mm:ss" } }, new DataGridViewTextBoxColumn { Name = "Countdown", HeaderText = "倒计时", Width = 100, ReadOnly = true }, new DataGridViewTextBoxColumn { Name = "Status", HeaderText = "状态", Width = 80, ReadOnly = true } }); // 加载本地CSV数据 LoadDataFromCsv(@"tasks.csv"); // 启动全局倒计时Timer,1秒触发一次 var timer = new System.Windows.Forms.Timer { Interval = 1000 }; timer.Tick += Timer_Tick; timer.Start(); }
三、独立倒计时实现(全局Timer方案)
用单个Timer统一更新所有行,避免多Timer资源浪费:
private void Timer_Tick(object sender, EventArgs e) { foreach (DataGridViewRow row in dataGridView1.Rows) { // 跳过新行 if (row.IsNewRow) continue; if (DateTime.TryParse(row.Cells["EndDate"].Value?.ToString(), out DateTime endDate)) { TimeSpan remaining = endDate - DateTime.Now; if (remaining > TimeSpan.Zero) { // 更新倒计时和状态 row.Cells["Countdown"].Value = $"{remaining.Hours:D2}:{remaining.Minutes:D2}:{remaining.Seconds:D2}"; row.Cells["Status"].Value = "占用"; } else { row.Cells["Countdown"].Value = "已结束"; row.Cells["Status"].Value = "空闲"; } } } }
四、输入代码自动填充数据
监听CellEndEdit事件,当用户完成代码输入后自动填充日期并保存:
private void DataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e) { // 仅处理代码列(索引0) if (e.ColumnIndex != dataGridView1.Columns["Code"].Index || e.RowIndex < 0) return; DataGridViewRow row = dataGridView1.Rows[e.RowIndex]; string code = row.Cells["Code"].Value?.ToString()?.Trim(); if (string.IsNullOrEmpty(code)) return; // 避免重复填充 if (row.Cells["StartDate"].Value != null) return; // 填充开始、结束日期 DateTime now = DateTime.Now; row.Cells["StartDate"].Value = now; row.Cells["EndDate"].Value = now.AddMinutes(45); // 立即保存到CSV SaveDataToCsv(@"tasks.csv"); }
五、CSV文件读写实现
写入全表到CSV
private void SaveDataToCsv(string filePath) { using (StreamWriter sw = new StreamWriter(filePath, false, Encoding.UTF8)) { // 写入表头 sw.WriteLine("Code,StartDate,EndDate"); foreach (DataGridViewRow row in dataGridView1.Rows) { if (row.IsNewRow) continue; string code = row.Cells["Code"].Value?.ToString() ?? ""; string startDate = row.Cells["StartDate"].Value?.ToString() ?? ""; string endDate = row.Cells["EndDate"].Value?.ToString() ?? ""; // 处理含逗号的内容(用双引号包裹) code = code.Contains(",") ? $"\"{code}\"" : code; sw.WriteLine($"{code},{startDate},{endDate}"); } } }
从CSV加载数据
private void LoadDataFromCsv(string filePath) { if (!File.Exists(filePath)) return; dataGridView1.Rows.Clear(); using (StreamReader sr = new StreamReader(filePath, Encoding.UTF8)) { // 跳过表头 sr.ReadLine(); string line; while ((line = sr.ReadLine()) != null) { string[] parts = line.Split(','); if (parts.Length < 3) continue; // 解析带双引号的内容 string code = parts[0].Trim('"'); string startDate = parts[1]; string endDate = parts[2]; dataGridView1.Rows.Add(code, startDate, endDate, "", ""); } } // 加载后立即触发一次倒计时更新 Timer_Tick(null, null); }
六、关键细节说明
- 日期格式统一:读写CSV时用
yyyy-MM-dd HH:mm:ss格式,避免解析错误 - 重复填充判断:在
CellEndEdit中判断开始日期是否已填充,防止重复触发 - 新行处理:遍历时跳过DataGridView的默认新行,避免空数据错误
- 编码问题:读写CSV用UTF8编码,避免中文乱码
内容的提问来源于stack exchange,提问作者Manuele
相关产品推荐
相关产品推荐

