Каждый рецепт имеет три поля: имя, категория и калорийность. Проект разделен на три отдельных уровня в соответствии со стандартом MVVM — Model, ViewModel и View как отдельные проекты в одном решении.
Базовая функциональность работает нормально. Рецепты загружаются при запуске, в списке они отображаются правильно, а нажатие кнопки «Добавить» создает новый рецепт и добавляет его в список. Текстовые поля ввода также очищаются после нажатия кнопки, что говорит мне о том, что уведомления PropertyChanged работают правильно для свойств ввода в VMMainWindow.
Проблема начинается, когда я выбираю элемент из списка и пытаюсь отобразить его сведения в текстовых блоках ниже. Текстовые блоки привязаны к SelectedRezept.Name, SelectedRezept.Kategorie и SelectedRezept.Kalorien.
При первом выборе они показывают правильные значения, что означает, что сам путь привязки правильный и SelectedRezept установлен правильно.
Однако при запуске метода, который изменяет одно из этих свойств выбранного VMRezept объект, текстовый блок не обновляется. Я могу подтвердить в отладчике, что базовое значение изменяется — пользовательский интерфейс просто не улавливает это.
Отображение обновляется только в том случае, если я нажимаю на другой элемент и затем повторно выбираю исходный, что означает, что WPF не уведомляется об изменении свойства.
Я заметил, что мои установщики в VMRezept вообще не вызывают PropertyChanged — они просто передают значение напрямую в модель. У меня есть вспомогательный метод OnPropertyChanged, определенный в VMRezept, но я никогда его нигде не вызываю. Я не уверен, является ли это основной причиной или реальная проблема заключается в том, что PropertyChanged в дочернем объекте модели представления не перемещается автоматически в родительский VMMainWindow, где находится привязка.
Я попробовал снова запустить PropertyChanged для SelectedRezept из VMMainWindow после вызова метода, и это приводит к обновлению текстовых блоков, но уведомлять об этом кажется неправильным что весь выбранный объект изменился, хотя на самом деле изменилось только одно свойство внутри него.
Мои вопросы:
- Должен ли каждый установщик в VMRezept вызывать PropertyChanged, даже если значения поступают из уровня модели?
- Вызывает ли вручную повторный запуск PropertyChanged для SelectedRezept из родительского элемента является стандартным подходом, или существует более чистый шаблон для этого?
- PropertyChanged в дочерней модели представления когда-либо автоматически распространяется на привязки в родительском элементе, или WPF прослушивает только объект, с которым он напрямую связан?
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ViewModel
{
public class VMMainWindow : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
ObservableCollection rezepteListe;
VMRezept aktRezept;
private UserCommand buttonCommand;
private string neuerName;
public string NeuerName
{
get => neuerName;
set
{
neuerName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NeuerName)));
}
}
private string neueKategorie;
public string NeueKategorie
{
get => neueKategorie;
set
{
neueKategorie = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NeueKategorie)));
}
}
private int neueKalorien;
public int NeueKalorien
{
get => neueKalorien;
set
{
neueKalorien = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NeueKalorien)));
}
}
public ObservableCollection RezepteListe { get => rezepteListe; set => rezepteListe = value; }
public VMRezept AktRezept { get => aktRezept; set => aktRezept = value; }
public UserCommand ButtonCommand { get => buttonCommand; set => buttonCommand = value; }
public VMMainWindow()
{
rezepteListe = new ObservableCollection();
Fuellen();
this.ButtonCommand = new UserCommand(new Action(VMMWNeuesRezept));
}
private void VMMWNeuesRezept(object obj)
{
VMRezept neuesRezept = new VMRezept(NeuerName, NeueKategorie, NeueKalorien);
rezepteListe.Add(neuesRezept);
NeuerName = "";
NeueKategorie = "";
NeueKalorien = 0;
}
public void Fuellen()
{
RezepteListe.Add(new VMRezept("Pasta Aglio e Olio", "vegetarisch", 400));
RezepteListe.Add(new VMRezept("Pfannkuchen", "Dessert", 450));
RezepteListe.Add(new VMRezept("Gulasch", "Hauptgericht", 940));
}
}
}