You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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上的总价文本也不会更新。

修复方案

  1. 优化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逻辑,否则商品相关字段不会自动刷新。

  1. 给LblTotalCart补充属性变更通知,和OrdersList的写法保持一致:
private string _lblTotalCart;
public string LblTotalCart
{
    get => _lblTotalCart;
    set
    {
        _lblTotalCart = value;
        OnPropertyChanged();
    }
}
  1. 重构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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 19:09:21