.NET MAUI中未保存的编辑页对象变更意外同步至列表页问题
问题描述
我有一个.NET MAUI Shell应用,ListPage通过CollectionView展示基于SQLite存储(同步至Azure SQL数据库)的缓存对象集合。点击CollectionView中的项会弹出底部弹窗显示对象详情,弹窗的编辑按钮通过Shell.Current.GoToAsync跳转到EditPage并传递对象参数,EditPage通过ApplyQueryAttributes获取对象。
保存功能正常:点击EditPage的保存按钮时,会通过WeakReferenceMessenger通知ListPage添加/编辑对象并更新CollectionView。但存在问题:编辑对象属性(名称、日期等)后不点击保存,直接调用Shell.Current.GoToAsync("..")返回时,未保存的变更会显示在ListPage的底部弹窗中;刷新ListPage的缓存项时,这些未保存变更也会反映在CollectionView里,只有同步SQLite存储后,未保存变更才会被丢弃、恢复原始值。
解决方案
核心原因
问题出在对象引用传递:跳转到EditPage时传递的是原对象的引用,EditPage中修改属性时直接修改了ListPage缓存里的同一个实例,所以即使不保存,ListPage绑定的实例已经被修改,UI会自动更新显示变更。
解决步骤
1. 跳转EditPage时传递对象副本
不要直接传递原对象实例,而是创建一个完全独立的副本,让EditPage修改副本而非原实例:
给SurgicalActivity创建副本的方式有两种:
- 手动复制属性:
async Task Edit(object item) { if (item is SurgicalActivity originalActivity) { // 创建对象副本,复制所有需要的属性 var activityCopy = new SurgicalActivity { Id = originalActivity.Id, MainProcedure = originalActivity.MainProcedure, Date = originalActivity.Date, Comments = originalActivity.Comments, // 复制其他所有业务属性 }; await Shell.Current.GoToAsync($"{nameof(ClinicalActivityEditPage)}", new Dictionary<string, object> { { "activity", activityCopy } }); } }
- 实现
ICloneable接口简化复制:
先给SurgicalActivity实现接口:
public class SurgicalActivity : ICloneable { // 原有属性... public object Clone() { return new SurgicalActivity { Id = this.Id, MainProcedure = this.MainProcedure, Date = this.Date, Comments = this.Comments, // 其他属性 }; } }
然后修改Edit方法:
async Task Edit(object item) { if (item is SurgicalActivity originalActivity) { var activityCopy = (SurgicalActivity)originalActivity.Clone(); await Shell.Current.GoToAsync($"{nameof(ClinicalActivityEditPage)}", new Dictionary<string, object> { { "activity", activityCopy } }); } }
2. 保存时同步副本变更到原缓存实例
EditPage保存成功后,通过WeakReferenceMessenger传递修改后的副本,ListPage收到消息后,找到缓存中的原实例并更新其属性(或替换原实例):
// ListPage中注册消息的处理逻辑 WeakReferenceMessenger.Default.Register<ActivityUpdatedMessage>(this, (recipient, message) => { var updatedActivity = message.Value; // 从分组集合中找到原实例 var originalActivity = GroupedActivities .SelectMany(group => group.Items) // 假设GroupedActivities是分组集合,Items是组内的列表 .FirstOrDefault(a => a.Id == updatedActivity.Id); if (originalActivity != null) { // 覆盖原实例的属性 originalActivity.MainProcedure = updatedActivity.MainProcedure; originalActivity.Date = updatedActivity.Date; originalActivity.Comments = updatedActivity.Comments; // 更新其他属性 } // 通知UI刷新(如果使用ObservableCollection可省略此步) OnPropertyChanged(nameof(GroupedActivities)); });
3. 可选:使用ObservableCollection优化UI更新
如果GroupedActivities的组内列表是ObservableCollection<SurgicalActivity>,替换原实例后会自动触发UI更新,无需手动调用OnPropertyChanged:
// 找到原实例的索引并替换 var group = GroupedActivities.First(g => g.Items.Contains(originalActivity)); var index = group.Items.IndexOf(originalActivity); group.Items[index] = updatedActivity;
效果说明
修改后,EditPage中编辑的是独立副本,原实例在ListPage缓存中保持原始状态,不保存返回时ListPage显示的还是原始值;只有点击保存时,才会把副本的变更同步到原实例,确保UI只显示已确认的修改。
内容的提问来源于stack exchange,提问作者FabianK

