WPF 遇到挂钩集合更改事件的问题



我有一个数据网格,我需要在其中计算嵌套数据网格的价格列的总和,如下所示:

图像

我正在尝试遵循此示例,以便每个 Person 对象的 Items 可观察集合在更改时收到通知。不同之处在于我在类中实现它,而不是视图模型。

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

不要在ViewModels之间使用事件处理程序 - 这是黑魔法,可能会因为创建的引用而使你内存泄漏。

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

我知道它不漂亮,但它有效

我认为有一个简单的解决方案...

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

通常,当列表更改时,总数会发生变化。 您仍然需要另一笔款项,以防商品价格发生变化......但这将是不太常见的情况。

我最终弄清楚了原始代码的问题所在。我使用了这个构造函数:

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

相关内容

  • 没有找到相关文章

最新更新