Xamarin ObservableCollection修改子项后界面不更新问题求助
Xamarin购物车页面操作后数据不更新问题修复
问题现象
开发Xamarin购物车(Cart Page)页面时,点击商品添加/移除按钮后,页面数据无法同步更新。
对应页面效果:
原有ViewModel核心代码:
public class CartViewModel : BindableObject { .... private ObservableCollection<OrderDisplay> _ordersList; public ObservableCollection<OrderDisplay> OrdersList { get => _ordersList; set { _ordersList = value; OnPropertyChanged(); } } }
原有按钮触发的AddTotal逻辑:
private async void AddTotal(OrderDisplay oCart, int quantity) { decimal total = 0; int index = 0; if (oCart != null) { foreach (OrderDisplay obj in OrdersList) { if (obj.Id == oCart.Id) { break; } index += 1; } OrdersList[index].Quantity = quantity; OrdersList[index].Total = quantity * OrdersList[index].Price; //OrdersList = null; //OrdersList = tempOrdersList; var order = await _apiService.GetOrderById(OrdersList[index].Id, token); order.Data.Quantity = OrdersList[index].Quantity; var orderUpdated = await _apiService.UpdateOrder(Convert.ToString(order.Data.Id), token, order.Data); if (!orderUpdated) { await _messageService.ShowMessageAsync("Error", "Ha ocurrido un error.", "Ok", ""); return; } } foreach (OrderDisplay order in OrdersList) { total = order.Total + total; } LblTotalCart = string.Format("{0:N2}€", total); }
尝试给OrderDisplay实现INotifyPropertyChanged接口时,运行抛出NullReference空引用异常,原有实现代码:
public class OrderDisplay : INotifyPropertyChanged { //public int Id { get; set; } //public int Quantity { get; set; } //public decimal Price { get; set; } //public decimal Total { get; set; } //public CrProduct Product { get; set; } private int id; public int Id { get { return id; } set { id = value; OnPropertyChanged("Id"); } } public int quantity; public int Quantity { get { return quantity; } set { quantity = value; OnPropertyChanged("Quantity"); } } public decimal price; public decimal Price { get { return price; } set { price = value; OnPropertyChanged("Price"); } } public decimal total; public decimal Total { get { return total; } set { total = value; OnPropertyChanged("Total"); } } public CrProduct product; public CrProduct Product { get { return product; } set { product = value; OnPropertyChanged("Product"); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }
问题根因
ObservableCollection<T>仅在集合本身发生增删、重置、替换整项操作时触发UI更新通知,集合内单个元素的属性值修改不会被集合自动监听,直接修改OrdersList[index].Quantity这类属性时,UI收不到变更通知自然不会刷新。- 之前实现
INotifyPropertyChanged时抛出空引用,和接口实现逻辑无关,是原有代码存在两个边界漏洞:一是手动遍历维护索引时,如果没找到Id匹配的商品,索引值会等于集合长度,访问OrdersList[index]会直接触发索引越界/空引用;二是API返回结果没有做空值校验,如果接口返回null,直接访问order.Data也会抛空引用。 - 注释掉的
OrdersList = null; OrdersList = tempOrdersList;属于强制重置集合的取巧方案,会导致列表闪烁、滚动位置丢失,性能差,不推荐使用。 - 原有代码里的
LblTotalCart没有实现属性变更通知,就算计算出了新的总价,UI上的总价文本也不会更新。
修复方案
- 优化
OrderDisplay的INotifyPropertyChanged实现,用[CallerMemberName]自动获取属性名,避免硬编码字符串写错属性名导致通知失效,同时增加通用的SetProperty方法简化代码:
using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; public class OrderDisplay : INotifyPropertyChanged { private int _id; public int Id { get => _id; set => SetProperty(ref _id, value); } private int _quantity; public int Quantity { get => _quantity; set => SetProperty(ref _quantity, value); } private decimal _price; public decimal Price { get => _price; set => SetProperty(ref _price, value); } private decimal _total; public decimal Total { get => _total; set => SetProperty(ref _total, value); } private CrProduct _product; public CrProduct Product { get => _product; set => SetProperty(ref _product, value); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(storage, value)) return false; storage = value; OnPropertyChanged(propertyName); return true; } }
注意:如果
CrProduct类的属性(比如商品名称、图片地址)也绑定到了购物车UI,需要给CrProduct也实现相同的INotifyPropertyChanged逻辑,否则商品相关字段不会自动刷新。
- 给
LblTotalCart补充属性变更通知,和OrdersList的写法保持一致:
private string _lblTotalCart; public string LblTotalCart { get => _lblTotalCart; set { _lblTotalCart = value; OnPropertyChanged(); } }
- 重构
AddTotal方法,去掉手动维护索引的逻辑,增加全链路非空校验,从根源解决空引用问题:
// 记得引入LINQ命名空间:using System.Linq; private async void AddTotal(OrderDisplay oCart, int quantity) { decimal total = 0; if (oCart != null) { // 直接查找匹配的商品项,避免手动维护索引导致越界 var targetItem = OrdersList.FirstOrDefault(obj => obj.Id == oCart.Id); if (targetItem == null) return; targetItem.Quantity = quantity; targetItem.Total = quantity * targetItem.Price; // 接口返回结果加非空校验 var order = await _apiService.GetOrderById(targetItem.Id, token); if (order?.Data == null) { await _messageService.ShowMessageAsync("Error", "Ha ocurrido un error.", "Ok", ""); return; } order.Data.Quantity = targetItem.Quantity; var orderUpdated = await _apiService.UpdateOrder(Convert.ToString(order.Data.Id), token, order.Data); if (!orderUpdated) { await _messageService.ShowMessageAsync("Error", "Ha ocurrido un error.", "Ok", ""); return; } } foreach (var item in OrdersList) { total += item.Total; } LblTotalCart = string.Format("{0:N2}€", total); }
额外注意事项
- 不要通过整体重新赋值
OrdersList的方式触发刷新,只要集合内元素正确实现了INotifyPropertyChanged,修改属性时UI会自动响应更新。 - 如果按钮是通过CommandParameter传递当前商品对象,确认XAML里的绑定路径正确,不要传错对象导致找不到匹配的商品项。
- 如果修改后还是不刷新,检查页面的BindingContext是否正确绑定到
CartViewModel实例,列表的ItemSource是否正确绑定OrdersList属性。
内容的提问来源于stack exchange,提问作者user18972374
相关产品推荐
相关产品推荐

