que - Implementación de WPF MVVM INotifyPropertyChanged: modelo o modelo de vista
observablecollection methods (3)
He leído varios debates sobre dónde implementar INotifyPropertyChanged aquí en StackOverflow y otros blogs, pero parece que hay casos en los que debe implementarlo en el Modelo. Aquí está mi situación: estoy buscando comentarios sobre mi conclusión o mi enfoque es incorrecto.
Estoy usando esta implementación de un ObservableDictionary ( ObservableDictionary ) porque necesito consultas con la clave.
En este diccionario coloco la colección de objetos Modelo.
En mi máquina virtual, declaro una instancia (libros) del diccionario y en el XAML se unen a ella.
<tk:DataGrid AutoGenerateColumns="False" Grid.Row="1" ItemsSource="{Binding Mode=TwoWay, Path=Books.Store}" Grid.ColumnSpan="2" Margin="3">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Binding="{Binding Mode=TwoWay, Path=Value.Name}" MinWidth="100" Header="Name" />
<tk:DataGridTextColumn Binding="{Binding Mode=TwoWay, Path=Value.Details}" MinWidth="300" Header="Details" />
</tk:DataGrid.Columns>
</tk:DataGrid>
Si implemento INotifyPropertyChanged en la VM para libros y cambie el valor de un nombre de libro en el código, la interfaz de usuario no se actualizará.
Si implemento INotifyPropertyChanged en la VM para Store y cambie el valor de un nombre de libro en el código, la UI no se actualizará.
Si implemento INotifyProperyChanged en el modelo y cambio el valor de un nombre de libro en el código, la interfaz de usuario se actualiza.
El evento modificado no se activa en el primer caso porque no se llama al setter del diccionario, es el elemento (un libro).
Me estoy perdiendo algo, porque si esta es la interpretación correcta, si quiero notificaciones consistentes para mis Modelos independientemente de si están directamente vinculados a XAML oa través de algún tipo de colección, siempre me gustaría que el Modelo implemente INotifyProperyChanged.
Por cierto, además de la referencia dll, personalmente no veo INotifyPropertyChanged como una función de interfaz de usuario - creo que debe definirse en un espacio de nombres .net más general - mis 2 centavos.
LA EDICIÓN COMIENZA AQUÍ:
Estábamos teniendo un debate de semántica tan bueno que me perdí el núcleo de mi pregunta, por lo que aquí se publica nuevamente pero con un ejemplo de MVVM muy simple para ilustrar mi pregunta.
Los modelos:
public class Book
{
public string Title { get; set; )
public List<Author> Authors { get; set; }
}
public class Author
{
public string Name { get; set; }
}
El proveedor de datos para generar algunos datos ficticios
public class BookProvider
{
public ObservableCollection<Book> GetBooks() {
ObservableCollection<Book> books = new ObservableCollection<Book>();
books.Add(new Book {
Title = "Book1",
Authors = new List<Author> { new Author { Name = "Joe" }, new Author { Name = "Phil" } }
});
books.Add(new Book {
Title = "Book2",
Authors = new List<Author> { new Author { Name = "Jane" }, new Author { Name = "Bob" } }
});
return books;
}
}
The ViewModel
public class BookViewModel : INotifyPropertyChanged
{
private ObservableCollection<Book> books;
public ObservableCollection<Book> Books {
get { return books; }
set {
if (value != books) {
books = value;
NotifyPropertyChanged("Books");
}
}
}
private BookProvider provider;
public BookViewModel() {
provider = new BookProvider();
Books = provider.GetBooks();
}
// For testing the example
public void MakeChange() {
Books[0].Title = "Changed";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
El código XAML detrás normalmente no lo haría de esta manera, solo por un simple ejemplo
public partial class MainWindow : Window
{
private BookViewModel vm;
public MainWindow() {
InitializeComponent();
vm = new BookViewModel();
this.DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e) {
vm.MakeChange();
}
}
El XAML
<Window x:Class="BookTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="242*" />
<RowDefinition Height="69*" />
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Books}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Title}" />
<ListBox ItemsSource="{Binding Authors}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontStyle="Italic" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1" Content="Change" Click="Button_Click" />
</Grid>
Como está codificado anteriormente, cuando hago clic en el botón y cambio el valor en el primer Libro, la IU no cambia.
Sin embargo, cuando muevo INotifyPropertyChanged al modelo, funciona bien (actualizaciones de UI) porque el cambio se produce en el conjunto de propiedades Model y no en los libros de la máquina virtual:
public class Book : INotifyPropertyChanged
{
private string title;
public string Title {
get { return title; }
set {
if (value != title) {
title = value;
NotifyPropertyChanged("Title");
}
}
}
public List<Author> Authors { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Volviendo a mi pregunta original, ¿cómo logro esto sin implementar INotifyPropertyChanged en el modelo?
Gracias.
La cuestión es que si estuviera siguiendo MVVM, tendría un BookViewModel
para su clase de modelo Book
. Por lo tanto, tendría una implementación INotifyPropertyChanged
en ese modelo de vista. Exactamente para ese propósito existe MVVM (pero no solo).
Dicho esto, INotifyPropertyChanged
tiene que implementarse en clases de modelos de visualización, no en modelos.
ACTUALIZACIÓN : En respuesta a su actualización y nuestra discusión en comentarios ...
Por BookViewModel
quise decir algo más. Debe envolver en este modelo de vista no toda la colección de objetos de Book
, sino un Book
individual:
public class BookViewModel : INotifyPropertyChanged
{
private Book book;
public Book Book {
get { return book; }
}
public string Title {
get { return Book.Title; }
set {
Book.Title = value;
NotifyPropertyChanged("Title");
}
}
public BookViewModel(Book book) {
this.book = book;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Y su BookProvider
devolverá ObservableCollection<BookViewModel>
lugar de ObservableCollection<Book>
:
public class BookProvider
{
public ObservableCollection<BookViewModel> GetBooks() {
ObservableCollection<BookViewModel> books = new ObservableCollection<BookViewModel>();
books.Add(new BookViewModel(new Book {
Title = "Book1",
Authors = new List<Author> { new Author { Name = "Joe" }, new Author { Name = "Phil" } }
}));
books.Add(new BookViewModel(new Book {
Title = "Book2",
Authors = new List<Author> { new Author { Name = "Jane" }, new Author { Name = "Bob" } }
}));
return books;
}
}
Como puede ver, al actualizar la propiedad Title
del Book
lo hará a través de la propiedad Title
del modelo de vista correspondiente que generará el evento PropertyChanged
, que activará la actualización de UI.
No confunda INotifyPropertyChanged con MVVM.
Considera lo que realmente es INotifyPropertyChanged -> Es un evento que se dispara para decir "Hey, mira, he cambiado". Si a alguien le importa, puede hacer algo al respecto, ya sea que se trate de una Vista, un Modelo de Vista o lo que sea.
Comencemos con su Libro (Modelo). La propiedad Título puede activar un evento modificado, ¿por qué no? Tiene sentido, el Libro trata con sus propias propiedades.
Ahora para BookViewModel: genial, ¡no necesitamos duplicar el título y aumentar nuestro código! Whoo!
Considere una vista en la que desee ver una lista de libros, o un libro con una lista de autores. Su ViewModel puede manejar propiedades adicionales específicas para la vista, como IsSelected. Este es un gran ejemplo: ¿por qué le importaría al Libro si se seleccionó o no? Esta es la responsabilidad del ViewModel.
Obviamente, lo anterior depende de su arquitectura, pero personalmente, si estoy creando una biblioteca de objetos, implementaré una clase base con INotifyPropertyChanged y las propiedades del objeto serán responsables de activar el evento.
Por favor lea este artículo . Explica cómo puede reducir la duplicación de código implementando INotifyPropertyChanged
en el modelo.