2012-10-02 121 views
4

我有兩個類,一個用於ViewModel和一個用於產品。 產品分類中有一個名爲的線路,線路總計爲,而ViewModel分類有一個名爲的總額。 Product類綁定到DataGrid,用戶 插入數量,隨後自動更新Line Total通知/綁定父財產來計算子財產的總和

這裏是視圖模型類:

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"); 
       } 

      } 

     } 
} 

我的問題是:如何在總金額可以計算所有產品總和[總共]?孩子如何產品可以通知父母ViewModel更新TotalAmount

喜歡的東西:

foreach(var product in Products) 
{ 
    TotalAmount += product.LineTotal; 
} 

回答

7

實現此目的的一種方法是每次用戶編輯一行總數並每次向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> 
+0

如果我不取消訂閱產品項目將從產品集合中刪除的'PropertyChanged'事件,那好嗎?我有關於'CollectionChanged'事件的相同問題(可能在Dispose方法中?)。 – Gromy

0

我想總金額的UI中的值,當你設定總金額,這樣一NotifyPropertyChanged事件被觸發纔會更新。爲此,您必須監聽所有產品的PropertyChangedEvent,並且當集合發生更改或產品的LineTotal發生更改時,必須將TotalAmount設置爲與_TotalAmount不同的某個值。

但是這段代碼真的很難理解:目前還不清楚爲什麼你存儲一個值,每次在一個變量(_TotalAmount)中讀取時計算出來的值(TotalAmount)。並且由於_TotalAmount未設置爲零不是正確的值。

1

如果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"); 
    } 
} 
0

我相信bernd_rausch的回答是正確的。基本問題是,爲什麼你要在你的ViewModel中存儲TotalAmount?唯一的原因可能是你有太多的產品會影響性能。但即使在這種情況下,您也必須謹慎,保持價值一致。

最安全的方法是編寫一個TotalAmount屬性,用於即時計算TotalAmount。然後鏈接已更改的事件。

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"); 
    } 

} 

我忽略了重置案例。但我認爲這應該給你正確的方向。如果你想緩存計算結果,我仍然會使用這個方法,通過一個在其中一個變更處理程序中被重置的內部延遲值進行緩存。