#c_sharp #wpf #mvvm
Есть приложение в котором пользователь может указывать название продукта и его цену, по нажатию на кнопку эти данные вносятся в ObservableCollection и сразу же отображаются в DataGrid. Также в окне присутствует textbox в котором должна отображаться средняя цена всех внесенных продуктов. Проблема в следующем: Нужно чтобы пользователь в DataGrid мог изменять цену уже внесённых продуктов после чего сразу же должна изменяться средняя цена (AvaragePrice) всех продуктов, а этого не происходит, данные в коллекции изменяются, но изменения вышеуказанного свойства не происходит: MainWindow.xaml:MainViewModel: class MainViewModel : BaseModel { MainModel mainModel; public MainViewModel() { // Инициализируем модель, подписываемся на изменение любого из её свойств mainModel = new MainModel(); mainModel.PropertyChanged += (s, e) => { OnPropertyChanged(e.PropertyName); }; } public string Name { get { return mainModel.Name; } set { mainModel.Name = value; OnPropertyChanged(); } } public double Price { get { return mainModel.Price; } set { mainModel.Price = value; OnPropertyChanged(); } } public RelayCommand AddProduct { get { return mainModel.AddProduct; } } public ObservableCollection AllProducts { get { return mainModel.AllProducts; } set { mainModel.AllProducts = value; OnPropertyChanged(); } } public double AvaragePrice { get { return mainModel.AvaragePrice; } set { mainModel.AvaragePrice = value; OnPropertyChanged(); } } } MainModel: class MainModel : BaseModel { public MainModel() { allProducts = new ObservableCollection (); allProducts.CollectionChanged += (s,e) => { AvaragePrice = allProducts.Sum(x => x.Price) / allProducts.Count; }; } string name; public string Name { get { return name; } set { name = value; OnPropertyChanged(); } } double price; public double Price { get { return price; } set { price = value; OnPropertyChanged(); } } ObservableCollection allProducts; public ObservableCollection AllProducts { get { return allProducts; } set { allProducts = value; OnPropertyChanged(); AvaragePrice = allProducts.Sum(x => x.Price) / allProducts.Count; } } RelayCommand addProduct; public RelayCommand AddProduct { get { return addProduct ?? (addProduct = new RelayCommand(obj => { Product newProduct = new Product() { Price = this.Price, Name = this.Name }; AllProducts.Add(newProduct); })); } } double avaragePrice; public double AvaragePrice { get { return avaragePrice; } set { avaragePrice = value; OnPropertyChanged(); } } }
Ответы
Ответ 1
Смотрите в общем как можно поступить (за основу взял ответ с En SO)... Подготовка XAML разметка Для примера нам (мне) понадобится набросать небольшой View:INotifyPropertyChanged Также нам понадобится INotifyPropertyChanged, для удобства создадим отдельный класс: public class VM : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } Реализация Итак, скажем, у нас есть VM для наших предметов и некая модель самого предмета: C моделью предмета думаю все понятно, просто объявляем необходимые свойства (в моем случае это имя и цена). public class ItemModel : VM { public string Name { get; set; } private int price; public int Price { get => price; set { price = value; OnPropertyChanged(); } } } В VM у нас пока будет все тоже, но реализуем коллекцию предметов и среднюю цену, а также, давайте сделаем метод, который будет обновлять цену: public class ItemsViewModel : VM { public ObservableCollection Items { get; set; } = new ObservableCollection (); private int avaragePrice; public int AvaragePrice { get => avaragePrice; set { avaragePrice = value; OnPropertyChanged(); } } public void UpdatePrice() { AvaragePrice = Items.Sum(x => x.Price) / Items.Count; } } И так, теперь у вас есть выбор 1. Подписывать все ItemModel на событие изменение. 2. Использовать BindingList. 1. Используем ObservableCollection Для начала подпишемся на событие изменения коллекции в нашей VM: public ItemsViewModel() { Items.CollectionChanged += ItemsOnCollectionChanged; } private void ItemsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (INotifyPropertyChanged item in e.OldItems) item.PropertyChanged -= UpdatePrice; } if (e.NewItems != null) { foreach (INotifyPropertyChanged item in e.NewItems) item.PropertyChanged += UpdatePrice; } } Изменим немного метод обновления: public void UpdatePrice(object sender, PropertyChangedEventArgs e) { AvaragePrice = Items.Sum(x => x.Price) / Items.Count; } Вроде все... И так, что здесь происходит? Суть в следующем: ObservableCollection оповещает только если в коллекцию добавляется, либо что то удаляется. В этом случае мы при добавление предмета в коллекцию проходимся по всем его значениям и подписываемся на событие изменения, если наш предмет в коллекции реализует INotifyPropertyChanged. При удаление делаем обратное, то есть отписываемся. Таким образом, все Model внутри коллекции будут подписаны на событие обновление цены. 2. Используем BindingList Есть довольно классная штука в WPF, как BindingList. У нее есть событие ListChanged, которое в свою очередь оповещает о любом (вроде) изменении в коллекции. Перепишем ObservalCollection на BindingList . В конструкторе подпишемся на событие изменения, ну и обновление цены можно положить внутрь, я думаю...: public ItemsViewModel() { Items.ListChanged += ItemsOnListChanged; } private void ItemsOnListChanged(object sender, ListChangedEventArgs e) { if (e.ListChangedType == ListChangedType.ItemChanged) { AvaragePrice = Items.Sum(x => x.Price) / Items.Count; } } public BindingList Items { get; set; } = new BindingList (); Ну тут, я думаю, все понятно и объяснять не нужно. Если у нас значение изменено, то обновляем цену (тут можете тип выбрать нужный). В общем, результат у нас будет примерно следующий:
Комментариев нет:
Отправить комментарий