2017-03-28 2 views
0

ich ein Datagrid, wo ich brauche die Summe der Preisspalte eines verschachtelten Datenraster zu berechnen, etwa so:WPF Probleme Collection Ereignisse Einhaken

Image

Ich versuche this example zu folgen, so dass mein Items beobachtbare Sammlung für jedes Person-Objekt wird über Änderungen benachrichtigt. Der Unterschied ist, dass ich es in einer Klasse implementiere, nicht in 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); 
     } 
    } 

Der Code innerhalb der Konstruktor Ereignisse nicht einhaken, wenn ein neues Objekt initialisiert wird oder ein vorhandenes bearbeitet wurde. Daher wird das Items_PropertyChanged-Ereignis nie ausgelöst. Ich kann nur die gesamte Liste manuell aktualisieren. Was mache ich hier falsch?

Oder gibt es vielleicht einen anderen Ansatz, um die Summe für die Einkaufsliste jeder Person zu berechnen?

Unten ist der gesamte Code, wenn jemand sich auch interessiert es anzuschauen.

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

Antwort

0

nicht zwischen Eventhandler ViewModels Verwenden Sie - diese schwarze Magie und kann bringen u zu Speicherlecks, weil die erstellten Referenzen.

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

Ich weiß, es ist nicht schön, aber es funktioniert

+0

Ich versuche, Ihrer Logik zu folgen. Was muss ich als Argument an ein Item-Objekt übergeben, wenn ich es in ViewModel initialisiere? var smtn = neues Element (?) {ItemName = "Telefon", Preis = 220}; – Disodium

0

Ich denke, es eine einfache Lösung ist ...

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

Im Allgemeinen ist die Gesamtsumme, wenn die Liste Änderungen ändern. Sie brauchen immer noch die andere Summe, falls sich der Preis eines Artikels ändert ... aber das wäre die seltenere Situation.

+0

Mein Problem besteht darin, dass das Items_CollectionChanged -Ereignis nie ausgelöst wird, wenn ich Item zu observablecollection hinzufüge. Diese Codezeile wird also nicht ausgeführt. – Disodium

+0

Das Items_CollectionChanged-Ereignis wird ausgelöst. Das Items_PropertyChanged-Ereignis wird nicht ausgelöst. Der einzige Grund dafür, dass das Items_CollectIonChanged-Ereignis nicht ausgelöst wird, wenn Sie der Auflistung ein Element hinzufügen, ist, wenn ein anderer Ereignishandler eine Ausnahme auslöst. – AQuirky

0

Ich habe schließlich herausgefunden, was mit meinem ursprünglichen Code falsch war. Ich habe diesen Konstruktor:

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

Das beigefügte Ereignis wurde dann von diesem initializer effektiv überschreibt:

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

Deshalb ist das Ereignis nie ausgelöst. Es war nicht da! Der richtige Weg ist ein parametrischer Konstruktor zu verwenden:

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

Und dann initialisieren es etwa so:

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

Und das war es. Funktioniert jetzt wie ein Zauber. Unten ist der vollständige Code des ursprünglichen Beispiels nachbearbeitet:

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