WinForm中跟踪Candidate实体未保存更改,关闭前提示用户的优雅实现
WinForm未保存数据提示与数据恢复的优雅实现方案
核心思路
实现需求的关键是维护实体的原始数据快照,通过对比当前窗体数据与快照的差异判断是否存在未保存修改,再根据用户选择执行保存、放弃或取消操作。
具体实现步骤
1. 保存原始数据快照
在窗体进入编辑模式时,创建Candidate实体的深拷贝作为原始快照,确保原始数据与当前编辑数据完全独立,避免引用共享导致的意外修改。
// 窗体类内声明私有字段 private Candidate _originalCandidate; // 原始数据快照 private Candidate _currentCandidate; // 绑定到窗体的当前实体 // 初始化编辑模式(可在窗体加载或Edit按钮点击事件中调用) private void StartEdit(Candidate targetCandidate) { // 深拷贝生成原始快照,这里用Json序列化实现通用深拷贝 _originalCandidate = DeepCopy(targetCandidate); _currentCandidate = targetCandidate; // 启用所有编辑字段 ToggleFieldsEnabled(true); // 同步实体数据到窗体控件 SyncCandidateToForm(_currentCandidate); } // 通用深拷贝方法 private T DeepCopy<T>(T obj) { var jsonString = System.Text.Json.JsonSerializer.Serialize(obj); return System.Text.Json.JsonSerializer.Deserialize<T>(jsonString)!; }
2. 检测未保存修改
实现对比逻辑,判断当前实体与原始快照是否存在属性差异,以此确定是否有未保存的修改。
方式1:手动对比属性(适合属性较少的场景)
private bool HasUnsavedChanges() { return !Equals(_originalCandidate.Id, _currentCandidate.Id) || !Equals(_originalCandidate.FirstName, _currentCandidate.FirstName) || !Equals(_originalCandidate.MiddleName, _currentCandidate.MiddleName) || !Equals(_originalCandidate.LastName, _currentCandidate.LastName) || !Equals(_originalCandidate.DateOfBirth, _currentCandidate.DateOfBirth) || !Equals(_originalCandidate.Gender, _currentCandidate.Gender) || !Equals(_originalCandidate.Nationality, _currentCandidate.Nationality) || !Equals(_originalCandidate.Education, _currentCandidate.Education); }
方式2:反射自动对比(适合属性较多的场景)
private bool HasUnsavedChanges() { var properties = typeof(Candidate).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); foreach (var prop in properties) { var originalValue = prop.GetValue(_originalCandidate); var currentValue = prop.GetValue(_currentCandidate); if (!Equals(originalValue, currentValue)) { return true; } } return false; }
3. 窗体关闭时的提示与处理
重写窗体的FormClosing事件,在事件中检查未保存修改,根据用户选择执行对应操作。
private void CandidateForm_FormClosing(object sender, FormClosingEventArgs e) { if (!HasUnsavedChanges()) return; var dialogResult = MessageBox.Show("存在未保存的修改,是否保存?", "提示", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning); switch (dialogResult) { case DialogResult.Yes: // 执行保存逻辑,例如调用数据层更新数据库 SaveCandidateToDatabase(_currentCandidate); break; case DialogResult.No: // 放弃修改,直接关闭窗体 break; case DialogResult.Cancel: // 取消关闭操作,留在当前窗体 e.Cancel = true; break; } }
4. 取消编辑时恢复原始数据
如果需要在点击“取消”按钮或用户选择取消关闭时恢复原始数据,实现数据恢复方法:
private void RestoreOriginalData() { // 将原始快照的值覆盖当前实体 _currentCandidate.Id = _originalCandidate.Id; _currentCandidate.FirstName = _originalCandidate.FirstName; _currentCandidate.MiddleName = _originalCandidate.MiddleName; _currentCandidate.LastName = _originalCandidate.LastName; _currentCandidate.DateOfBirth = _originalCandidate.DateOfBirth; _currentCandidate.Gender = _originalCandidate.Gender; _currentCandidate.Nationality = _originalCandidate.Nationality; _currentCandidate.Education = _originalCandidate.Education; // 同步恢复后的实体数据到窗体控件 SyncCandidateToForm(_currentCandidate); // 禁用编辑字段 ToggleFieldsEnabled(false); } // 辅助方法:将实体数据同步到窗体控件 private void SyncCandidateToForm(Candidate candidate) { txtFirstName.Text = candidate.FirstName; txtMiddleName.Text = candidate.MiddleName ?? string.Empty; txtLastName.Text = candidate.LastName; dtpDateOfBirth.Value = candidate.DateOfBirth ?? DateTime.Today; cboGender.Text = candidate.Gender ?? string.Empty; txtNationality.Text = candidate.Nationality ?? string.Empty; txtEducation.Text = candidate.Education ?? string.Empty; } // 辅助方法:切换字段编辑状态 private void ToggleFieldsEnabled(bool isEnabled) { txtFirstName.Enabled = isEnabled; txtMiddleName.Enabled = isEnabled; txtLastName.Enabled = isEnabled; dtpDateOfBirth.Enabled = isEnabled; cboGender.Enabled = isEnabled; txtNationality.Enabled = isEnabled; txtEducation.Enabled = isEnabled; }
优化建议
- 使用
BindingSource进行数据绑定,让实体属性与窗体控件自动同步,减少手动编写同步代码的工作量。 - 将快照、对比、恢复逻辑封装成通用的
EditStateManager类,可在多个WinForm中复用,提升代码复用性。
内容的提问来源于stack exchange,提问作者Unis
相关产品推荐
相关产品推荐

