我有两个类,一个用于 ViewModel ,另一个用于产品。 Product类具有名为行总计的属性,ViewModel类具有名为总金额的属性。 Product类绑定到DataGrid和用户 随后插入数量并自动更新行总计。
以下是 ViewModel 类:
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Product> products { get; set; }// the children
private decimal _TotalAmount;
public decimal TotalAmount // <=== has to hold sum of [products.LineTotal]
{
get
{
return totalAmount;
}
set
{
if (value != _TotalAmount)
{
_TotalAmount = value;
onPropertyChanged(this, "TotalAmount");
}
}
}
以下是产品类,它是一个孩子:
public class Product : INotifyPropertyChanged
{
private decimal _LineTotal;
public decimal LineTotal
{
get
{
return _LineTotal;
}
set
{
if (value != _LineTotal)
{
_LineTotal = value;
onPropertyChanged(this, "LineTotal");
}
}
}
}
我的问题是: TotalAmount 如何计算所有产品的总和 [行总数] ?儿童产品如何通知家长 ViewModel 更新 TotalAmount ?
类似的东西:
foreach(var product in Products)
{
TotalAmount += product.LineTotal;
}
答案 0 :(得分:8)
实现此目的的一种方法是,每次用户编辑行总计时以及每次从ObservableCollection
添加或删除产品时重新计算总金额。
由于Product
实现INotifyPropertyChanged
并在设置新行总计时引发PropertyChanged
事件,ViewModel
可以处理该事件并重新计算总金额。
ObservableCollection
在添加或删除项目时会引发CollectionChanged
事件,因此ViewModel
也可以处理该事件并重新计算。 (如果产品只能更改,而不是由用户添加/删除等,则此部分不是必需的。)
你可以试试这个小程序,看看它是如何完成的:
<强>代码隐藏强>
public partial class MainWindow : Window
{
ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
vm.Products = new ObservableCollection<Product>
{
new Product { Name = "Product1", LineTotal = 10 },
new Product { Name = "Product2", LineTotal = 20 },
new Product { Name = "Product3", LineTotal = 15 }
};
this.DataContext = vm;
}
private void AddItem(object sender, RoutedEventArgs e)
{
vm.Products.Add(new Product { Name = "Added product", LineTotal = 50 });
}
private void RemoveItem(object sender, RoutedEventArgs e)
{
vm.Products.RemoveAt(0);
}
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get { return _products; }
set
{
_products = value;
// We need to know when the ObservableCollection has changed.
// On added products: hook up eventhandlers to their PropertyChanged events.
// On removed products: recalculate the total.
_products.CollectionChanged += (sender, e) =>
{
if (e.NewItems != null)
AttachProductChangedEventHandler(e.NewItems.Cast<Product>());
else if (e.OldItems != null)
CalculateTotalAmount();
};
AttachProductChangedEventHandler(_products);
}
}
private void AttachProductChangedEventHandler(IEnumerable<Product> products)
{
// Attach eventhandler for each products PropertyChanged event.
// When the LineTotal property has changed, recalculate the total.
foreach (var p in products)
{
p.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == "LineTotal")
CalculateTotalAmount();
};
}
CalculateTotalAmount();
}
public void CalculateTotalAmount()
{
// Set TotalAmount property to the sum of all line totals.
TotalAmount = Products.Sum(p => p.LineTotal);
}
private decimal _TotalAmount;
public decimal TotalAmount
{
get { return _TotalAmount; }
set
{
if (value != _TotalAmount)
{
_TotalAmount = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("TotalAmount"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Product : INotifyPropertyChanged
{
public string Name { get; set; }
private decimal _LineTotal;
public decimal LineTotal
{
get { return _LineTotal; }
set
{
if (value != _LineTotal)
{
_LineTotal = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("LineTotal"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
<强> XAML:强>
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<DataGrid ItemsSource="{Binding Products}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" />
<DataGridTextColumn Binding="{Binding LineTotal}" />
</DataGrid.Columns>
</DataGrid>
<Button Click="AddItem">Add item</Button>
<Button Click="RemoveItem">Remove item</Button>
<TextBlock>
<Run>Total amount:</Run>
<Run Text="{Binding TotalAmount}" />
</TextBlock>
</StackPanel>
</Window>
答案 1 :(得分:2)
如果ParentViewModel
关注ChildModel
上的某个属性何时更新,则应该订阅其PropertyChanged
事件。
但是,由于您拥有ChildModels
的集合,因此应在PropertyChanged
事件中添加/删除挂接CollectionChanged
事件的处理程序。
// Hook up CollectionChanged event in Constructor
public MyViewModel()
{
Products = new ObservableCollection<Product>();
MyItemsSource.CollectionChanged += Products_CollectionChanged;
}
// Add/Remove PropertyChanged event to Product item when the collection changes
void Products_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(Product item in e.NewItems)
item.PropertyChanged += Product_PropertyChanged;
if (e.OldItems != null)
foreach(Product item in e.OldItems)
item.PropertyChanged -= Product_PropertyChanged;
}
// When LineTotal property of Product changes, re-calculate Total
void Product_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "LineTotal")
{
TotalAmount = products.Sum(p => p.LineTotal);
// Or if calculation is in the get method of the TotalAmount property
//onPropertyChanged(this, "TotalAmount");
}
}
答案 2 :(得分:0)
我认为只有在设置TotalAmount以便触发NotifyPropertyChanged事件时,才会更新UI中TotalAmount的值。为此,您必须监听所有产品的PropertyChangedEvent,当产品集合更改或产品的LineTotal发生更改时,您必须将TotalAmount设置为与_TotalAmount不同的某个值。
但是这段代码真的很难理解:不清楚为什么要存储一个值,每次在变量(_TotalAmount)中读取它(TotalAmount)时计算一个值。由于_TotalAmount未设置为零,因此不是正确的值。
答案 3 :(得分:0)
我相信bernd_rausch的回答是朝着正确的方向发展的。基本问题是为什么要将TotalAmount存储在ViewModel中?唯一的原因可能是您有这么多产品会影响性能。但即使在这种情况下,您也必须小心保持价值一致。
最安全的方法是编写一个TotalAmount属性,即可动态计算TotalAmount。然后链接Changed事件。
public class ViewModel : INotifyPropertyChanged
{
ViewModel()
{
Products = new ObservableCollection<Product>();
Products.CollectionChanged += OnProductsChanged;
}
public ObservableCollection<Product> Products { get; private set; }// the children
public decimal TotalAmount { get { return Products.Select(p => p.LineTotal).Sum(); } }
private void OnProductChanged(object sender, PropertyChangedEventArgs eventArgs)
{
if("LineTotal" != eventArgs.PropertyName)
return;
onPropertyChanged(this, "TotalAmount");
}
private void OnProductsChanged(object sender, NotifyCollectionChangeEventArgs eventArgs)
{
// This ignores a collection Reset...
// Process old items first, for move cases...
if (eventArgs.OldItems != null)
foreach(Product item in eventArgs.OldItems)
item.PropertyChanged -= OnProductChanged;
if (eventArgs.NewItems != null)
foreach(Product item in eventArgs.NewItems)
item.PropertyChanged += OnProductChanged;
onPropertyChanged(this, "TotalAmount");
}
}
我忽略了重置案例。但我认为这应该给你正确的方向。如果要缓存计算结果,我仍然会使用此方法,通过在其中一个更改处理程序中重置的内部惰性值进行缓存。