C# Xamarin.Forms:ObservableCollection内对象属性变更不触发集合属性变更事件
问题
我用ObservableCollection<StockInfo>存储农产品信息,通过BindableLayout和DataTemplate绑定到StackLayout展示条目。用户可以通过Entry修改StockInfo的Price属性,修改能成功作用到对象,但不会触发集合的setter和PropertyChanged事件。
我添加了SumPrices属性,用LINQ计算所有Price的总和,页面加载时计算正确,但修改Entry后总和不会更新。调试发现StockInfo的属性变更会触发自身的PropertyChanged,但集合的get/set断点根本不会命中。我需要集合在内部对象属性变更时,触发自身的PropertyChanged事件,来执行后续逻辑。
解决方案
核心原因
ObservableCollection默认只监听集合元素的增减/替换,不会监听元素内部属性的变化。所以修改元素属性时,集合本身不会触发PropertyChanged,自然也不会通知SumPrices更新。
具体实现步骤
- 给每个
StockInfo对象绑定PropertyChanged事件:当集合添加新元素时,订阅元素的PropertyChanged事件,在事件回调中触发SumPrices的PropertyChanged。 - 处理集合元素移除的情况:如果有元素被移除,取消订阅事件,避免内存泄漏。
- 优化绑定类型转换:
Price是int类型,绑定到Entry.Text(字符串)时,添加类型转换器,避免类型错误。
修改后的完整代码
namespace Test { public class Testing : BaseContentPage, INotifyPropertyChanged { public class StockInfo : INotifyPropertyChanged { private string _description; public string Description { get => _description; set { _description = value; OnPropertyChanged(nameof(Description)); } } private int _price; public int Price { get => _price; set { _price = value; OnPropertyChanged(nameof(Price)); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } private ObservableCollection<StockInfo> _stockItems; public ObservableCollection<StockInfo> StockItems { get => _stockItems; set { if (_stockItems != null) { // 移除旧集合元素的事件订阅 foreach (var item in _stockItems) { item.PropertyChanged -= StockItem_PropertyChanged; } } _stockItems = value; OnPropertyChanged(nameof(StockItems)); if (_stockItems != null) { // 给新集合元素订阅事件 foreach (var item in _stockItems) { item.PropertyChanged += StockItem_PropertyChanged; } } OnPropertyChanged(nameof(SumPrices)); } } public double SumPrices { get => StockItems?.Sum(p => p.Price) ?? 0; } DataTemplate StockTemplate = new DataTemplate(() => { return new StackLayout { Orientation = StackOrientation.Horizontal, Children = { new Entry().Bind(Entry.TextProperty, nameof(StockInfo.Description), Mode=BindingMode.TwoWay), new Entry { Keyboard = Keyboard.Numeric }.Bind(Entry.TextProperty, nameof(StockInfo.Price), Mode=BindingMode.TwoWay, Converter=new IntToStringConverter()) } }; }); public Testing() { BindingContext = this; StockItems = new ObservableCollection<StockInfo>(); StockItems.Add(new StockInfo { Description = "Milk", Price = 20 }); StockItems.Add(new StockInfo { Description = "Cheese", Price = 15 }); Content = new StackLayout { Children = { new StackLayout { }.Invoke(layout => BindableLayout.SetItemTemplate(layout, StockTemplate)) .Bind(BindableLayout.ItemsSourceProperty, nameof(StockItems)), new Label().Bind(Label.TextProperty, nameof(SumPrices), StringFormat="总价: {0:C}") } }; } private void StockItem_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(StockInfo.Price)) { OnPropertyChanged(nameof(SumPrices)); } } } // int与string双向转换转换器 public class IntToStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value?.ToString(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (int.TryParse(value?.ToString(), out int result)) { return result; } return 0; } } }
额外说明
StockInfo是数据模型,不需要继承BaseContentPage,直接实现INotifyPropertyChanged即可。- 新增的
IntToStringConverter解决了int类型与Entry.Text字符串的双向绑定转换问题,避免输入非数字时出错。 StockItems的setter中处理了旧集合的事件取消订阅,防止内存泄漏。SumPrices添加了空值判断,避免集合为null时抛出异常。- 绑定指定
Mode=BindingMode.TwoWay,确保输入修改能正确同步到数据源。
内容的提问来源于stack exchange,提问作者chiken nuget
相关产品推荐
相关产品推荐

