在 WPF/C# 中通知 observable collection 中的项目更改
Notify item changes in observable collection in WPF/C#
在我的 WPF 项目中,我有一个 Service
EF模型定义为
public class Service
{
public int ID
public string Name
public decimal Price
}
在我的视图模型中我有。
public class ReceiptViewModel : BindableBase
{
private ObservableCollection<Service> _services;
public ObservableCollection<Service> Services
{
get { return _services; }
set { SetProperty(ref _services, value, () => RaisePropertyChanged(nameof(Total))); }
}
public decimal Total => Services.Sum(s => s.Price);
}
Total 绑定到我视图中的文本块,我的可观察对象 collection 绑定到内部带有文本框的 itemscontrol。
我希望每次用户从 UI 更改我的 collection 中的价格时,我的 Total 文本块都会更改。我怎样才能做到这一点?
这很简单,但很乏味:
- 创建一个实现
INotifyPropertyChanged
的 ServiceViewModel
- 将数据库中的集合映射到视图模型的可观察集合 (
ReceiptViewModel.Services
)
- 侦听可观察集合的所有更改并附加到所有添加项的
PropertyChanged
(并从所有已删除项分离,同时附加所有最初存在的项)
- 每当
ServiceViewModels
之一发生变化时,设置新的 ReceiptViewModel.Total
(或者如果你将其设为计算 属性 则提高 ReceiptViewModel.PropertyChanged
)
- 当
ServiceViewModel
或视图模型集合发生变化时也更新数据库
如果您改用 EventAggregator
,则第 3 步可能 easier/simpler/more 明显,也可能不明显。
你应该自己实现它,我的可观察集合示例(你还需要订阅并在集合项更改时引发 Raise OnPropertyChanged(nameof(Total))),或者将我的 collectionEx 实现更改为引发集合更改事件.
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public ObservableCollectionEx(IEnumerable<T> initialData) : base(initialData)
{
Init();
}
public ObservableCollectionEx()
{
Init();
}
private void Init()
{
foreach (T item in Items)
item.PropertyChanged += ItemOnPropertyChanged;
CollectionChanged += FullObservableCollectionCollectionChanged;
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (T item in e.NewItems)
{
if (item != null)
item.PropertyChanged += ItemOnPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (T item in e.OldItems)
{
if (item != null)
item.PropertyChanged -= ItemOnPropertyChanged;
}
}
}
private void ItemOnPropertyChanged(object sender, PropertyChangedEventArgs e)
=> ItemChanged?.Invoke(sender, e);
public event PropertyChangedEventHandler ItemChanged;
}
您的问题不是很清楚 - 您当前的实施情况如何?有什么错误吗?我们应该注意的任何断点分析?您的绑定是如何在 Xaml
中实现的
在提供进一步信息之前,这里是您所提供内容的通用答案。
我将对您的 ReceiptViewModel 进行以下更改,充分利用 Prism MVVM。
public class ReceiptViewModel : BindableBase
{
private ObservableCollection<Service> _services;
public ObservableCollection<Service> Services
{
get { return _services; }
set { SetProperty(ref _services, value); }
}
private decimal _total;
public decimal Total
{
get { return _total; }
set { SetProperty(ref _total, Services.Sum(s => s.Price)); }
}
public DelegateCommand PriceChangedCommand {get; set;}
public ReceiptViewModel()
{
PriceChangedCommand = new DelegateCommand(OnPriceChanged);
}
private void OnPriceChanged()
{
Total = Services.Sum(s=>s.Price);
}
}
现在在你的 XAML 中(不确定当前的样子)所以这是一个例子
<!--Ensure your DataContext / ItemsSource is set correctly-->
<TextBox Text={Binding Price}>
<b:InteractionTriggers>
<b:EventTrigger EventName="TextChanged">
<prism:InvokeCommandAction Command={Binding DataContext.PriceChangedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
<!--Your AncestorType will be your root (top level) e.g. Window/UserControl etc-->
</b:EventTrigger>
</b:InteractionTriggers>
</TextBox>
正如我所说,您可能不需要 RelativeSource 绑定 - 我不知道您当前的实现,因此我给出了一个更复杂的答案,可以在更大的场景中使用。但您可以尝试更简单的版本 -
<prism:InvokeCommandAction Command={Binding PriceChangedCommand} />
要将此类 行为 添加到 xaml,您可能需要使用“Blend for Visual Studio”(应该已经安装了 Visual Studio).或者通过在 Window/Usercontrol
中添加以下命名空间,尝试在不使用 Blend 的情况下执行此操作
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
并安装以下 Nuget 包 - Microsoft.Xaml.Behaviors.Wpf
我试着听从@Haukinger 和@Bandook 的建议,然后我做了这个
模特
public class Service
{
public string Name { get; set; }
public decimal Price { get; set; }
}
扩展模型的视图模型
public class ServiceViewModel : Service, INotifyPropertyChanged
{
private decimal price;
public event PropertyChangedEventHandler PriceChanged;
public event PropertyChangedEventHandler PropertyChanged;
public ServiceViewModel()
{
}
public ServiceViewModel(decimal value)
{
this.price = value;
}
public new decimal Price
{
get { return price; }
set
{
price = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged();
}
}
// Create the OnPropertyChanged method to raise the event
// The calling member's name will be used as the parameter.
protected void OnPropertyChanged([CallerMemberName] string price = null)
{
PriceChanged?.Invoke(this, new PropertyChangedEventArgs(price));
}
}
主视图模型
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _firstName = "TABET AOUL";
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value, ()=> RaisePropertyChanged(nameof(FullName))); }
}
private string _lastName = "Abdelkrim";
public string LastName
{
get { return _lastName; }
set { SetProperty(ref _lastName, value, () => RaisePropertyChanged(nameof(FullName))); }
}
public string FullName => $"{FirstName} {LastName}";
private ObservableCollection<ServiceViewModel> _services = new ObservableCollection<ServiceViewModel>();
public ObservableCollection<ServiceViewModel> Services
{
get { return _services; }
set { SetProperty(ref _services, value, () => RaisePropertyChanged(nameof(Total))); }
}
public decimal Total => Services.Sum(s => s.Price);
public DelegateCommand AddServiceCommand { get; set; }
public void AddService()
{
var item = new ServiceViewModel { Name = "Service", Price = 100 };
item.PriceChanged += Item_PriceChanged;
Services.Add(item);
RaisePropertyChanged(nameof(Total));
}
public MainWindowViewModel()
{
var item = new ServiceViewModel { Name = "Service", Price = 100 };
item.PriceChanged += Item_PriceChanged;
Services.Add(item);
AddServiceCommand = new DelegateCommand(new Action(AddService));
}
private void Item_PriceChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged(nameof(Total));
}
}
代码似乎工作正常,当我更改 collection 项目之一的价格时,我的 UI 中的总变化除了我添加新项目时,所以我手动调用了 RaisePropertyChanged在点击命令上。
欢迎大家有更优雅的解决方案
在我的 WPF 项目中,我有一个 Service
EF模型定义为
public class Service
{
public int ID
public string Name
public decimal Price
}
在我的视图模型中我有。
public class ReceiptViewModel : BindableBase
{
private ObservableCollection<Service> _services;
public ObservableCollection<Service> Services
{
get { return _services; }
set { SetProperty(ref _services, value, () => RaisePropertyChanged(nameof(Total))); }
}
public decimal Total => Services.Sum(s => s.Price);
}
Total 绑定到我视图中的文本块,我的可观察对象 collection 绑定到内部带有文本框的 itemscontrol。
我希望每次用户从 UI 更改我的 collection 中的价格时,我的 Total 文本块都会更改。我怎样才能做到这一点?
这很简单,但很乏味:
- 创建一个实现
INotifyPropertyChanged
的 - 将数据库中的集合映射到视图模型的可观察集合 (
ReceiptViewModel.Services
) - 侦听可观察集合的所有更改并附加到所有添加项的
PropertyChanged
(并从所有已删除项分离,同时附加所有最初存在的项) - 每当
ServiceViewModels
之一发生变化时,设置新的ReceiptViewModel.Total
(或者如果你将其设为计算 属性 则提高ReceiptViewModel.PropertyChanged
) - 当
ServiceViewModel
或视图模型集合发生变化时也更新数据库
ServiceViewModel
如果您改用 EventAggregator
,则第 3 步可能 easier/simpler/more 明显,也可能不明显。
你应该自己实现它,我的可观察集合示例(你还需要订阅并在集合项更改时引发 Raise OnPropertyChanged(nameof(Total))),或者将我的 collectionEx 实现更改为引发集合更改事件.
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public ObservableCollectionEx(IEnumerable<T> initialData) : base(initialData)
{
Init();
}
public ObservableCollectionEx()
{
Init();
}
private void Init()
{
foreach (T item in Items)
item.PropertyChanged += ItemOnPropertyChanged;
CollectionChanged += FullObservableCollectionCollectionChanged;
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (T item in e.NewItems)
{
if (item != null)
item.PropertyChanged += ItemOnPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (T item in e.OldItems)
{
if (item != null)
item.PropertyChanged -= ItemOnPropertyChanged;
}
}
}
private void ItemOnPropertyChanged(object sender, PropertyChangedEventArgs e)
=> ItemChanged?.Invoke(sender, e);
public event PropertyChangedEventHandler ItemChanged;
}
您的问题不是很清楚 - 您当前的实施情况如何?有什么错误吗?我们应该注意的任何断点分析?您的绑定是如何在 Xaml
中实现的在提供进一步信息之前,这里是您所提供内容的通用答案。
我将对您的 ReceiptViewModel 进行以下更改,充分利用 Prism MVVM。
public class ReceiptViewModel : BindableBase
{
private ObservableCollection<Service> _services;
public ObservableCollection<Service> Services
{
get { return _services; }
set { SetProperty(ref _services, value); }
}
private decimal _total;
public decimal Total
{
get { return _total; }
set { SetProperty(ref _total, Services.Sum(s => s.Price)); }
}
public DelegateCommand PriceChangedCommand {get; set;}
public ReceiptViewModel()
{
PriceChangedCommand = new DelegateCommand(OnPriceChanged);
}
private void OnPriceChanged()
{
Total = Services.Sum(s=>s.Price);
}
}
现在在你的 XAML 中(不确定当前的样子)所以这是一个例子
<!--Ensure your DataContext / ItemsSource is set correctly-->
<TextBox Text={Binding Price}>
<b:InteractionTriggers>
<b:EventTrigger EventName="TextChanged">
<prism:InvokeCommandAction Command={Binding DataContext.PriceChangedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
<!--Your AncestorType will be your root (top level) e.g. Window/UserControl etc-->
</b:EventTrigger>
</b:InteractionTriggers>
</TextBox>
正如我所说,您可能不需要 RelativeSource 绑定 - 我不知道您当前的实现,因此我给出了一个更复杂的答案,可以在更大的场景中使用。但您可以尝试更简单的版本 -
<prism:InvokeCommandAction Command={Binding PriceChangedCommand} />
要将此类 行为 添加到 xaml,您可能需要使用“Blend for Visual Studio”(应该已经安装了 Visual Studio).或者通过在 Window/Usercontrol
中添加以下命名空间,尝试在不使用 Blend 的情况下执行此操作xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
并安装以下 Nuget 包 - Microsoft.Xaml.Behaviors.Wpf
我试着听从@Haukinger 和@Bandook 的建议,然后我做了这个
模特
public class Service
{
public string Name { get; set; }
public decimal Price { get; set; }
}
扩展模型的视图模型
public class ServiceViewModel : Service, INotifyPropertyChanged
{
private decimal price;
public event PropertyChangedEventHandler PriceChanged;
public event PropertyChangedEventHandler PropertyChanged;
public ServiceViewModel()
{
}
public ServiceViewModel(decimal value)
{
this.price = value;
}
public new decimal Price
{
get { return price; }
set
{
price = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged();
}
}
// Create the OnPropertyChanged method to raise the event
// The calling member's name will be used as the parameter.
protected void OnPropertyChanged([CallerMemberName] string price = null)
{
PriceChanged?.Invoke(this, new PropertyChangedEventArgs(price));
}
}
主视图模型
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _firstName = "TABET AOUL";
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value, ()=> RaisePropertyChanged(nameof(FullName))); }
}
private string _lastName = "Abdelkrim";
public string LastName
{
get { return _lastName; }
set { SetProperty(ref _lastName, value, () => RaisePropertyChanged(nameof(FullName))); }
}
public string FullName => $"{FirstName} {LastName}";
private ObservableCollection<ServiceViewModel> _services = new ObservableCollection<ServiceViewModel>();
public ObservableCollection<ServiceViewModel> Services
{
get { return _services; }
set { SetProperty(ref _services, value, () => RaisePropertyChanged(nameof(Total))); }
}
public decimal Total => Services.Sum(s => s.Price);
public DelegateCommand AddServiceCommand { get; set; }
public void AddService()
{
var item = new ServiceViewModel { Name = "Service", Price = 100 };
item.PriceChanged += Item_PriceChanged;
Services.Add(item);
RaisePropertyChanged(nameof(Total));
}
public MainWindowViewModel()
{
var item = new ServiceViewModel { Name = "Service", Price = 100 };
item.PriceChanged += Item_PriceChanged;
Services.Add(item);
AddServiceCommand = new DelegateCommand(new Action(AddService));
}
private void Item_PriceChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged(nameof(Total));
}
}
代码似乎工作正常,当我更改 collection 项目之一的价格时,我的 UI 中的总变化除了我添加新项目时,所以我手动调用了 RaisePropertyChanged在点击命令上。
欢迎大家有更优雅的解决方案