【问题标题】:WPF troubles hooking CollectionChanged eventsWPF 无法挂接 CollectionChanged 事件
【发布时间】:2023-03-13 07:48:01
【问题描述】:

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

Image

我正在尝试关注this example,以便我的每个 Person 对象的 Items 可观察集合在更改时得到通知。不同之处在于我是在一个类中实现它,而不是在 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();
        }
    }
}

【问题讨论】:

    标签: wpf datagrid observablecollection propertychanged


    【解决方案1】:

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

    我知道它不漂亮,但它很管用

    【讨论】:

    • 我正在尝试遵循您的逻辑。在 ViewModel 中初始化 Item 对象时,我必须将什么作为参数传递给它? var smtn = new Item (?) { ItemName = "Phone", Price = 220 };
    【解决方案2】:

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

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

    通常,当列表发生变化时,总数会发生变化。如果某件商品的价格发生变化,您仍然需要其他金额……但这种情况不太常见。

    【讨论】:

    • 我的问题是,当我将项目添加到 observablecollection 时,Items_CollectionChanged 事件永远不会触发。所以这行代码没有执行。
    • Items_CollectionChanged 事件将触发。 Items_PropertyChanged 事件不会触发。将项目添加到集合时不会触发 Items_CollectIonChanged 事件的唯一原因是是否有另一个引发异常的事件处理程序。
    【解决方案3】:

    我终于弄清楚我的原始代码出了什么问题。我使用了这个构造函数:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-06
      • 2017-06-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多