2017-03-28 59 views
0

我有一個DataGrid,我需要計算總的嵌套DataGrid的Price列的,就像這樣:WPF煩惱掛鉤CollectionChanged事件

Image

我試圖按照this example讓我每個Person對象的Items observable集合都會在更改時得到通知。不同之處在於我在一個類中實現它,而不是View Model。

public class Person : NotifyObject 
    { 
     private ObservableCollection<Item> _items; 
     public ObservableCollection<Item> Items 
     { 
      get { return _items; } 
      set { _items = value; OnPropertyChanged("Items"); } 
     } 
     private string _name; 
     public string Name 
     { 
      get { return _name; } 
      set { _name = value; OnPropertyChanged("Name"); } 
     } 
     public double Total 
     { 
      get { return Items.Sum(i => i.Price); } 
      set { OnPropertyChanged("Total"); } 
     } 

     public Person() 
     { 
      Console.WriteLine("0001 Constructor"); 
      this.Items = new ObservableCollection<Item>(); 
      this.Items.CollectionChanged += Items_CollectionChanged; 
      this.Items.Add(new Item()); 
     } 
     private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
     { 
      Console.WriteLine("0002 CollectionChanged"); 
      if (e.NewItems != null) 
       foreach (Item item in e.NewItems) 
        item.PropertyChanged += Items_PropertyChanged; 

      if (e.OldItems != null) 
       foreach (Item item in e.OldItems) 
        item.PropertyChanged -= Items_PropertyChanged; 
     } 

     private void Items_PropertyChanged(object sender, PropertyChangedEventArgs e) 
     { 
      Console.WriteLine("0003 PropertyChanged"); 
      this.Total = Items.Sum(i => i.Price); 
     } 
    } 

當一個新的項目被初始化或現有已編輯的構造函數中的代碼不掛鉤的事件。因此,Items_PropertyChanged事件從不觸發。我只能手動刷新整個列表。我在這裏做錯了什麼?

或者也許有不同的方法來計算每個人的購買清單的總和?

下面是整個代碼,如果有人在乎太看它。

XAML

<Window x:Class="collection_changed_2.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:collection_changed_2" 
     mc:Ignorable="d" 
     Title="MainWindow" SizeToContent="Height" Width="525"> 
    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition /> 
     </Grid.RowDefinitions> 
     <DataGrid x:Name="DataGrid1" 
        Grid.Row="0" 
        ItemsSource="{Binding DataCollection}" 
        SelectedItem="{Binding DataCollectionSelectedItem}" 
        AutoGenerateColumns="False" 
        CanUserAddRows="false" > 
      <DataGrid.Columns> 
       <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="2*"/> 
       <DataGridTemplateColumn Header="Item/Price" Width="3*"> 
        <DataGridTemplateColumn.CellTemplate > 
         <DataTemplate> 
          <DataGrid x:Name="DataGridItem" 
             ItemsSource="{Binding Items}" 
             SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ItemsSelectedItem}" 
             Background="Transparent" 
             HeadersVisibility="None" 
             AutoGenerateColumns="False" 
             CanUserAddRows="false" > 
           <DataGrid.Columns> 
            <DataGridTextColumn Binding="{Binding ItemName}" Width="*"/> 
            <DataGridTextColumn Binding="{Binding Price}" Width="50"/> 
            <DataGridTemplateColumn Header="Button" Width="Auto"> 
             <DataGridTemplateColumn.CellTemplate> 
              <DataTemplate> 
               <StackPanel> 
                <Button Content="+" 
                  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.AddItem }" 
                  Width="20" Height="20"> 
                </Button> 
               </StackPanel> 
              </DataTemplate> 
             </DataGridTemplateColumn.CellTemplate> 
            </DataGridTemplateColumn> 
           </DataGrid.Columns> 
          </DataGrid> 
         </DataTemplate> 
        </DataGridTemplateColumn.CellTemplate> 
       </DataGridTemplateColumn> 
       <DataGridTextColumn Header="Total" Binding="{Binding Total, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="Auto"/> 
       <DataGridTemplateColumn Header="Buttons" Width="Auto"> 
        <DataGridTemplateColumn.CellTemplate> 
         <DataTemplate> 
          <StackPanel VerticalAlignment="Center"> 
           <Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.AddPerson}" Width="20" Height="20">+</Button> 
          </StackPanel> 
         </DataTemplate> 
        </DataGridTemplateColumn.CellTemplate> 
       </DataGridTemplateColumn> 
      </DataGrid.Columns> 
     </DataGrid> 
     <StackPanel Grid.Row="1" Margin="10"> 
      <Button Width="150" Height="30" Content="Refresh" Command="{Binding Refresh}" /> 
     </StackPanel> 
    </Grid> 
</Window> 

C#

using System; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.ComponentModel; 
using System.Linq; 
using System.Windows; 
using System.Windows.Data; 
using System.Windows.Input; 

namespace collection_changed_2 
{ 
    public class Item : NotifyObject 
    { 
     private string _itemName; 
     public string ItemName 
     { 
      get { return _itemName; } 
      set { _itemName = value; OnPropertyChanged("ItemName"); } 
     } 
     private double _price; 
     public double Price 
     { 
      get { return _price; } 
      set { _price = value; OnPropertyChanged("Price"); } 
     } 
    } 

    public class Person : NotifyObject 
    { 
     private ObservableCollection<Item> _items; 
     public ObservableCollection<Item> Items 
     { 
      get { return _items; } 
      set { _items = value; OnPropertyChanged("Items"); } 
     } 
     private string _name; 
     public string Name 
     { 
      get { return _name; } 
      set { _name = value; OnPropertyChanged("Name"); } 
     } 
     public double Total 
     { 
      get { return Items.Sum(i => i.Price); } 
      set { OnPropertyChanged("Total"); } 
     } 

     public Person() 
     { 
      Console.WriteLine("0001 Constructor"); 
      this.Items = new ObservableCollection<Item>(); 
      this.Items.CollectionChanged += Items_CollectionChanged; 
      this.Items.Add(new Item()); 
     } 
     private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
     { 
      Console.WriteLine("0002 CollectionChanged"); 
      if (e.NewItems != null) 
       foreach (Item item in e.NewItems) 
        item.PropertyChanged += Items_PropertyChanged; 

      if (e.OldItems != null) 
       foreach (Item item in e.OldItems) 
        item.PropertyChanged -= Items_PropertyChanged; 
     } 

     private void Items_PropertyChanged(object sender, PropertyChangedEventArgs e) 
     { 
      Console.WriteLine("0003 PropertyChanged"); 
      this.Total = Items.Sum(i => i.Price); 
     } 
    } 

    public abstract class NotifyObject : INotifyPropertyChanged 
    { 
     public event PropertyChangedEventHandler PropertyChanged; 
     protected void OnPropertyChanged(string property) 
     { 
      if (PropertyChanged != null) 
       PropertyChanged(this, new PropertyChangedEventArgs(property)); 
     } 
    } 

    public class RelayCommand : ICommand 
    { 
     private Action<object> executeDelegate; 
     readonly Predicate<object> canExecuteDelegate; 

     public RelayCommand(Action<object> execute, Predicate<object> canExecute) 
     { 
      if (execute == null) 
       throw new NullReferenceException("execute"); 
      executeDelegate = execute; 
      canExecuteDelegate = canExecute; 
     } 

     public RelayCommand(Action<object> execute) : this(execute, null) { } 

     public event EventHandler CanExecuteChanged 
     { 
      add { CommandManager.RequerySuggested += value; } 
      remove { CommandManager.RequerySuggested -= value; } 
     } 

     public bool CanExecute(object parameter) 
     { 
      return canExecuteDelegate == null ? true : canExecuteDelegate(parameter); 
     } 

     public void Execute(object parameter) 
     { 
      executeDelegate.Invoke(parameter); 
     } 
    } 

    public class ViewModel : NotifyObject 
    { 
     public ObservableCollection<Person> DataCollection { get; set; } 

     public Person DataCollectionSelectedItem { get; set; } 
     public Item ItemsSelectedItem { get; set; } 

     public RelayCommand AddPerson { get; private set; } 
     public RelayCommand AddItem { get; private set; } 
     public RelayCommand Refresh { get; private set; } 

     public ViewModel() 
     { 
      DataCollection = new ObservableCollection<Person> 
      { 
       new Person() { 
        Name = "Friedrich Nietzsche", 
        Items = new ObservableCollection<Item> { 
         new Item { ItemName = "Phone", Price = 220 }, 
         new Item { ItemName = "Tablet", Price = 350 }, 
        } 
       }, 
       new Person() { 
        Name = "Jean Baudrillard", 
        Items = new ObservableCollection<Item> { 
         new Item { ItemName = "Teddy Bear Deluxe", Price = 2200 }, 
         new Item { ItemName = "Pokemon", Price = 100 } 
        } 
       } 
      }; 

      AddItem = new RelayCommand(AddItemCode, null); 
      AddPerson = new RelayCommand(AddPersonCode, null); 
      Refresh = new RelayCommand(RefreshCode, null); 
     } 

     public void AddItemCode(object parameter) 
     { 
      var collectionIndex = DataCollection.IndexOf(DataCollectionSelectedItem); 
      var itemIndex = DataCollection[collectionIndex].Items.IndexOf(ItemsSelectedItem); 
      Item newItem = new Item() { ItemName = "Item_Name", Price = 100 }; 
      DataCollection[collectionIndex].Items.Insert(itemIndex + 1, newItem); 
     } 
     public void AddPersonCode(object parameter) 
     { 
      var collectionIndex = DataCollection.IndexOf(DataCollectionSelectedItem); 
      Person newList = new Person() 
      { 
       Name = "New_Name", 
       Items = new ObservableCollection<Item>() { new Item() { ItemName = "Item_Name", Price = 100 } } 
      }; 
      DataCollection.Insert(collectionIndex + 1, newList); 
     } 
     private void RefreshCode(object parameter) 
     { 
      CollectionViewSource.GetDefaultView(DataCollection).Refresh(); 
     } 
    } 

    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 
      this.DataContext = new ViewModel(); 
     } 
    } 
} 

回答

0

不要ViewModels之間使用事件處理 - 這是黑魔法和可以帶來u到內存泄漏,因爲創建引用。

public interface IUpdateSum 
{ 
    void UpdateSum(); 
} 


public class Person : IUpdateSum 
{ 

    /* ... */ 

    public void UpdateSum() 
    { 
     this.Total = Items.Sum(i => i.Price); 
    } 


    /* ... */ 
} 


public class Item 
{ 
    private IUpdateSum SumUpdate; 

    private double price; 

    public Item(IUpdateSum sumUpdate) 
    { 
     SumUpdate = sumUpdate; 
    } 

    public double Price 
    { 
     get 
     { 
      return price; 
     } 
     set 
     { 
      RaisePropertyChanged("Price"); 
      SumUpdate.UpdateSum(); 
     } 
    } 
} 

我知道這是不是漂亮,但它的工作原理

+0

我試圖按照你的邏輯。在ViewModel中初始化Item對象時,我必須傳遞什麼參數? var smtn = new Item(?){ItemName =「Phone」,Price = 220}; – Disodium

0

我覺得這是一個簡單的解決方案...

private void Items_CollectionChanged(object sender,NotifyCollectionChangedEventArgs e) 
    { 
     Console.WriteLine("0002 CollectionChanged"); 
     if (e.NewItems != null) 
      foreach (Item item in e.NewItems) 
       item.PropertyChanged += Items_PropertyChanged; 

     if (e.OldItems != null) 
      foreach (Item item in e.OldItems) 
       item.PropertyChanged -= Items_PropertyChanged; 
     this.Total = Items.Sum(i => i.Price); 
    } 

一般來說總量將在更改列表的變化。如果物品的價格發生變化,您仍然需要其他金額......但這種情況不太常見。

+0

我的問題是,當我添加一個項目到observablecollection時,Items_CollectionChanged事件從不會觸發。所以這行代碼不會執行。 – Disodium

+0

Items_CollectionChanged事件將觸發。 Items_PropertyChanged事件不會觸發。 Item_CollectIonChanged事件在向集合添加項目時不會觸發的唯一原因是是否存在引發異常的另一個事件處理程序。 – AQuirky

0

我終於弄清楚我的原代碼有什麼問題。我用這個構造方法:

public Person() 
     { 
      this.Items = new ObservableCollection<Item>(); 
      this.Items.CollectionChanged += Items_CollectionChanged; 
      this.Items.Add(new Item()); 
     } 

的附加事件,然後有效地這個初始化覆蓋:

Person newList = new Person() 
      { 
       Name = "New_Name", 
       Items = new ObservableCollection<Item>() { new Item() { ItemName = "Item_Name", Price = 100 } } 
      }; 

這就是爲什麼事件從來沒有發射。它不在那裏!正確的方法是使用一個參數的構造函數:

public Person(string initName, ObservableCollection<Item> initItems) 
     { 
      this.Name = initName; 
      this.Items = new ObservableCollection<Item>(); 
      this.Items.CollectionChanged += Items_CollectionChanged; 
      foreach (Item item in initItems) 
       this.Items.Add(item); 
     } 

然後初始化它像這樣:

Person newList = new Person("New_Name", new ObservableCollection<Item>() { new Item() { ItemName = "Item_Name", Price = 100 } }); 

,就是這樣。現在就像魅力一樣。以下是原始示例返工的完整代碼:

using System; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.ComponentModel; 
using System.Linq; 
using System.Windows; 
using System.Windows.Data; 
using System.Windows.Input; 

namespace collection_changed_4 
{ 
    public class Item : NotifyObject 
    { 
     private string _itemName; 
     public string ItemName 
     { 
      get { return _itemName; } 
      set { _itemName = value; OnPropertyChanged("ItemName"); } 
     } 
     private double _price; 
     public double Price 
     { 
      get { return _price; } 
      set { _price = value; OnPropertyChanged("Price"); } 
     } 
    } 

    public class Person : NotifyObject 
    { 
     private ObservableCollection<Item> _items; 
     public ObservableCollection<Item> Items 
     { 
      get { return _items; } 
      set { _items = value; OnPropertyChanged("Items"); } 
     } 
     private string _name; 
     public string Name 
     { 
      get { return _name; } 
      set { _name = value; OnPropertyChanged("Name"); } 
     } 
     public double Total 
     { 
      get { return Items.Sum(i => i.Price); } 
      set { OnPropertyChanged("Total"); } 
     } 

     public Person(string initName, ObservableCollection<Item> initItems) 
     { 
      Console.WriteLine("0001 Constructor"); 
      this.Name = initName; 
      this.Items = new ObservableCollection<Item>(); 
      this.Items.CollectionChanged += Items_CollectionChanged; 
      foreach (Item item in initItems) 
       this.Items.Add(item); 
     } 
     private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
     { 
      Console.WriteLine("0002 CollectionChanged"); 
      if (e.NewItems != null) 
       foreach (Item item in e.NewItems) 
        item.PropertyChanged += Items_PropertyChanged; 

      if (e.OldItems != null) 
       foreach (Item item in e.OldItems) 
        item.PropertyChanged -= Items_PropertyChanged; 
      OnPropertyChanged("Total"); 
     } 

     private void Items_PropertyChanged(object sender, PropertyChangedEventArgs e) 
     { 
      Console.WriteLine("0003 PropertyChanged"); 
      OnPropertyChanged("Total"); 
     } 
    } 

    public abstract class NotifyObject : INotifyPropertyChanged 
    { 
     public event PropertyChangedEventHandler PropertyChanged; 
     protected void OnPropertyChanged(string property) 
     { 
      if (PropertyChanged != null) 
       PropertyChanged(this, new PropertyChangedEventArgs(property)); 
     } 
    } 

    public class RelayCommand : ICommand 
    { 
     private Action<object> executeDelegate; 
     readonly Predicate<object> canExecuteDelegate; 

     public RelayCommand(Action<object> execute, Predicate<object> canExecute) 
     { 
      if (execute == null) 
       throw new NullReferenceException("execute"); 
      executeDelegate = execute; 
      canExecuteDelegate = canExecute; 
     } 

     public RelayCommand(Action<object> execute) : this(execute, null) { } 

     public event EventHandler CanExecuteChanged 
     { 
      add { CommandManager.RequerySuggested += value; } 
      remove { CommandManager.RequerySuggested -= value; } 
     } 

     public bool CanExecute(object parameter) 
     { 
      return canExecuteDelegate == null ? true : canExecuteDelegate(parameter); 
     } 

     public void Execute(object parameter) 
     { 
      executeDelegate.Invoke(parameter); 
     } 
    } 

    public class ViewModel : NotifyObject 
    { 
     public ObservableCollection<Person> DataCollection { get; set; } 

     public Person DataCollectionSelectedItem { get; set; } 
     public Item ItemsSelectedItem { get; set; } 

     public RelayCommand AddPerson { get; private set; } 
     public RelayCommand AddItem { get; private set; } 
     public RelayCommand Refresh { get; private set; } 

     public ViewModel() 
     { 
      DataCollection = new ObservableCollection<Person> 
      { 
       new Person("Friedrich Nietzsche", new ObservableCollection<Item> { 
         new Item { ItemName = "Phone", Price = 220 }, 
         new Item { ItemName = "Tablet", Price = 350 }, 
        }), 
       new Person("Jean Baudrillard", new ObservableCollection<Item> { 
         new Item { ItemName = "Teddy Bear Deluxe", Price = 2200 }, 
         new Item { ItemName = "Pokemon", Price = 100 } 
        }) 
      }; 

      AddItem = new RelayCommand(AddItemCode, null); 
      AddPerson = new RelayCommand(AddPersonCode, null); 
      Refresh = new RelayCommand(RefreshCode, null); 
     } 

     public void AddItemCode(object parameter) 
     { 
      var collectionIndex = DataCollection.IndexOf(DataCollectionSelectedItem); 
      var itemIndex = DataCollection[collectionIndex].Items.IndexOf(ItemsSelectedItem); 
      Item newItem = new Item() { ItemName = "Item_Name", Price = 100 }; 
      DataCollection[collectionIndex].Items.Insert(itemIndex + 1, newItem); 
     } 
     public void AddPersonCode(object parameter) 
     { 
      var collectionIndex = DataCollection.IndexOf(DataCollectionSelectedItem); 
      Person newList = new Person("New_Name", new ObservableCollection<Item>() { new Item() { ItemName = "Item_Name", Price = 100 } }); 
      DataCollection.Insert(collectionIndex + 1, newList); 
     } 
     private void RefreshCode(object parameter) 
     { 
      CollectionViewSource.GetDefaultView(DataCollection).Refresh(); 
     } 
    } 

    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 
      this.DataContext = new ViewModel(); 
     } 
    } 
}