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

ObservableCollection子项属性修改后视图不更新的问题排查

ObservableCollection子项属性修改后视图不更新的解决方法

问题描述

ObservableCollection能正常处理集合的增删操作并触发视图更新,但修改集合中ListItem的属性(如代码中的origin.Quantity--)时,视图无法自动刷新。已让ListItem继承实现INotifyPropertyChanged的BaseModel,且Quantity属性通过SetProperty实现,但问题仍存在。


相关代码片段

ViewModel中的ObservableCollection定义

public ObservableCollection<ListItem> Items
{
    get { return primaryBin.Items; }
    set { primaryBin.Items = value; OnPropertyChanged(); }
}

修改子项属性的业务代码

if (Found) 
{
    viewModel.IsBusy = true;

    // 检查是否已接收该产品
    ListItem origin = viewModel.Items.FirstOrDefault(i => i.EAN == labelText);

    updateErp = false;
    SupplierOrderLineForScan p = viewModel.lines.FirstOrDefault(pr => pr.Ean.Trim() == labelText.Trim());
    u = await Common.GetPickingUser();

    // 检查手推车上是否已有该产品
    ListItem bincontentDirection = viewModel.NotCurrentItems.FirstOrDefault(i => i.EAN == labelText);
    BinContent bincontentOrigin = viewModel.NotCurrentBinContents.Find(s => s.StockId == p.StockId);


    if (bincontentDirection == default(ListItem))
    {
        bincontentDirection = new ListItem { 
            Quantity = 0
            , EAN = origin.EAN, Name = origin.Name};
        viewModel.ItemsTransfer.Add(bincontentDirection);
    }
    bincontentDirection.Quantity++;
    origin.Quantity--;
}

基础ViewModel的INotifyPropertyChanged实现

protected bool SetProperty<T>(ref T backingStore, T value,
    [CallerMemberName]string propertyName = "",
    Action onChanged = null)
{
    if (EqualityComparer<T>.Default.Equals(backingStore, value))
        return false;

    backingStore = value;
    onChanged?.Invoke();
    OnPropertyChanged(propertyName);
    return true;
}

#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
    var changed = PropertyChanged;
    if (changed == null)
        return;

    changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion

ListItem及BaseModel定义

public class BaseModel : INotifyPropertyChanged
{
    protected bool SetProperty<T>(ref T backingStore, T value,
        [CallerMemberName] string propertyName = "",
        Action onChanged = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingStore, value))
            return false;

        backingStore = value;
        onChanged?.Invoke();
        OnPropertyChanged(propertyName);
        return true;
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        var changed = PropertyChanged;
        if (changed == null)
            return;

        changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

public class ListItem : BaseModel
{
    // BinConten id
    public string Id { get; set; }
    public bool Block { get; set; }
    public string Name { get; set; }
    public bool IsBlockIconVisible { get; set; }
    public string CodeSupplier { get; set; }
    public bool IsHeader { get; set; }
    public string Entry { get; set; }
    public String ProductID { get; set; }
    public bool showDeleteButton { get; set; }
    public bool showTransferButton { get; set; }
    public int BinID { get; set; }
    public int Piece { get; set; }

    private int _quantity;
    public int Quantity
    {
        get { return _quantity; }
        set { SetProperty(ref _quantity, value); }
    }

    public int BookedQuantity { get; set; }

    public int QuantityDefect { get; set; }

    public Color bgColor { get; set; }

    int _quantityDefect;
    public string EAN { get; set; }

    public string QuantityDisplay
    {
        get { return $"Q: {Quantity}(@{BookedQuantity})"; }
    }

    public string QuantityExpectedDisplay
    {
        get { return $"{Quantity} / {BookedQuantity}"; }
    }

    public string EANReference
    {
        get { return $"{EAN} / {CodeSupplier}"; }
    }

    public Style Style { get; set; }
}

问题分析与修复步骤

1. 修复BaseModel中重复触发PropertyChanged的问题

BaseModel的OnPropertyChanged方法重复触发了事件,可能导致通知异常,简化为以下代码:

protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

2. 为计算属性添加通知依赖

ListItem中的QuantityDisplay、QuantityExpectedDisplay属于计算属性,依赖Quantity和BookedQuantity,但原代码中这两个属性变化时不会触发计算属性的更新。需要修改如下:

private int _quantity;
public int Quantity
{
    get { return _quantity; }
    set 
    { 
        if (SetProperty(ref _quantity, value))
        {
            OnPropertyChanged(nameof(QuantityDisplay));
            OnPropertyChanged(nameof(QuantityExpectedDisplay));
        }
    }
}

private int _bookedQuantity;
public int BookedQuantity
{
    get { return _bookedQuantity; }
    set
    {
        if (SetProperty(ref _bookedQuantity, value))
        {
            OnPropertyChanged(nameof(QuantityDisplay));
            OnPropertyChanged(nameof(QuantityExpectedDisplay));
        }
    }
}

3. 确保异步操作在UI线程更新属性

代码中存在异步调用await Common.GetPickingUser();,后续更新属性时需要切换到UI线程执行(以Xamarin为例):

Device.BeginInvokeOnMainThread(() =>
{
    bincontentDirection.Quantity++;
    origin.Quantity--;
});

4. 确认视图绑定配置

确保视图中集合绑定为OneWay或TwoWay(默认集合绑定为OneWay),子项属性绑定正确指向Quantity或对应的计算属性。

内容的提问来源于stack exchange,提问作者Elgate

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:47:52