2017-03-28 72 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(); 
     } 
    } 
}