Estoy revisando algunos artículos MVVM, principalmente este y este .
Mi pregunta específica es: ¿Cómo comunico los cambios de modelo del modelo al modelo de vista?
En el artículo de Josh, no veo que él haga esto. ViewModel siempre solicita propiedades al modelo. En el ejemplo de Rachel, ella tiene el modelo implementado INotifyPropertyChanged
y genera eventos del modelo, pero son para consumo de la vista en sí (vea su artículo / código para más detalles sobre por qué hace esto).
En ninguna parte veo ejemplos en los que el modelo alerta a ViewModel de cambios en las propiedades del modelo. Esto me tiene preocupado de que quizás no se haga por alguna razón. ¿Existe un patrón para alertar a ViewModel de cambios en el modelo? Parecería ser necesario ya que (1) posiblemente hay más de 1 ViewModel para cada modelo, y (2) incluso si solo hay un ViewModel, alguna acción en el modelo podría resultar en el cambio de otras propiedades.
Sospecho que podría haber respuestas / comentarios del tipo "¿Por qué querrías hacer eso?" comentarios, así que aquí hay una descripción de mi programa. Soy nuevo en MVVM, así que quizás todo mi diseño sea defectuoso. Lo describiré brevemente.
Estoy programando algo que es más interesante (¡al menos para mí!) Que las clases de "Cliente" o "Producto". Estoy programando BlackJack.
Tengo una vista que no tiene ningún código detrás y solo se basa en la vinculación a propiedades y comandos en el modelo de vista (consulte el artículo de Josh Smith).
Para bien o para mal, tomé la actitud de que el modelo debería contener no solo clases como PlayingCard
, Deck
sino también la BlackJackGame
clase que mantiene el estado de todo el juego y sabe cuando el jugador se ha arruinado, el crupier tiene que robar cartas y cuál es la puntuación actual del jugador y del crupier (menos de 21, 21, busto, etc.).
De BlackJackGame
expongo métodos como "DrawCard" y se me ocurrió que cuando se saca una tarjeta, las propiedades como CardScore
, y IsBust
deben actualizarse y estos nuevos valores deben comunicarse al ViewModel. ¿Quizás eso es un pensamiento erróneo?
Uno podría adoptar la actitud de que ViewModel llamó al DrawCard()
método, por lo que debería saber pedir una puntuación actualizada y averiguar si está arruinado o no. Opiniones
En mi ViewModel, tengo la lógica para tomar una imagen real de una carta de juego (según el palo, el rango) y ponerla a disposición para la vista. El modelo no debería preocuparse por esto (quizás otro ViewModel solo usaría números en lugar de imágenes de cartas). Por supuesto, ¿quizás algunos me dirán que el Modelo ni siquiera debería tener el concepto de un juego de BlackJack y eso debería manejarse en el ViewModel?
OnBust
y la máquina virtual puede suscribirse a él. Supongo que también podría utilizar un enfoque de la IEA.Respuestas:
Si desea que sus modelos avisen a los ViewModels de los cambios, deben implementar INotifyPropertyChanged y los ViewModels deben suscribirse para recibir notificaciones de PropertyChange.
Su código podría verse así:
Pero, por lo general, esto solo es necesario si más de un objeto realizará cambios en los datos del modelo, lo que no suele ser el caso.
Si alguna vez tiene un caso en el que en realidad no tiene una referencia a su propiedad Model para adjuntar el evento PropertyChanged, puede usar un sistema de mensajería como Prism
EventAggregator
o MVVM LightMessenger
.Tengo una breve descripción general de los sistemas de mensajería en mi blog; sin embargo, para resumirlo, cualquier objeto puede transmitir un mensaje y cualquier objeto puede suscribirse para escuchar mensajes específicos. Por lo tanto, puede transmitir un
PlayerScoreHasChangedMessage
desde un objeto y otro objeto puede suscribirse para escuchar esos tipos de mensajes y actualizar suPlayerScore
propiedad cuando escuche uno.Pero no creo que esto sea necesario para el sistema que ha descrito.
En un mundo MVVM ideal, su aplicación está compuesta por sus ViewModels, y sus Modelos son los bloques que se utilizan para construir su aplicación. Por lo general, solo contienen datos, por lo que no tendrían métodos como
DrawCard()
(que estaría en un ViewModel)Por lo tanto, probablemente tenga objetos de datos de modelo simples como estos:
y tendrías un objeto ViewModel como
(Los objetos anteriores deberían implementarse todos
INotifyPropertyChanged
, pero lo dejé fuera por simplicidad)fuente
DrawCard()
método estaría en ViewModel, junto con su otra lógica de juego. En una aplicación MVVM ideal, debería poder ejecutar su aplicación sin la interfaz de usuario por completo, simplemente creando ViewModels y ejecutando sus métodos, como a través de un script de prueba o una ventana de símbolo del sistema. Los modelos suelen ser solo modelos de datos que contienen datos sin procesar y validación de datos básicos.DrawCardCommand()
estarían en ViewModel, pero supongo que podría tener unBlackjackGameModel
objeto que contuviera unDrawCard()
método que el comando llamara si quisieraRespuesta corta: depende de los detalles.
En su ejemplo, los modelos se están actualizando "por sí mismos" y, por supuesto, estos cambios deben propagarse de alguna manera a las vistas. Dado que las vistas solo pueden acceder directamente a los modelos de vista, significa que el modelo debe comunicar estos cambios al modelo de vista correspondiente. El mecanismo establecido para hacerlo es, por supuesto
INotifyPropertyChanged
, lo que significa que obtendrá un flujo de trabajo como este:PropertyChanged
evento de la modeloDataContext
, las propiedades están vinculadas, etc.PropertyChanged
y levanta el suyoPropertyChanged
en respuestaPor otro lado, si sus modelos contenían poca (o ninguna) lógica comercial, o si por alguna otra razón (como ganar capacidad transaccional) decidió dejar que cada modelo de vista "poseyera" su modelo envuelto, todas las modificaciones al modelo pasarían por el modelo de vista por lo que tal disposición no sería necesaria.
Describo este diseño en otra pregunta de MVVM aquí .
fuente
Tus opciones:
Como yo lo veo,
INotifyPropertyChanged
es parte fundamental de .Net. es decir, está enSystem.dll
. Implementarlo en su "Modelo" es similar a implementar una estructura de eventos.Si desea POCO puro, entonces tiene que manipular efectivamente sus objetos a través de proxies / servicios y luego su ViewModel es notificado de los cambios escuchando el proxy.
Personalmente, simplemente implemento libremente INotifyPropertyChanged y luego uso FODY para hacer el trabajo sucio por mí. Se ve y se siente POCO.
Un ejemplo (usando FODY para IL Weave the PropertyChanged Raisers):
luego puede hacer que su ViewModel escuche PropertyChanged para cualquier cambio; o cambios específicos de propiedad.
La belleza de la ruta INotifyPropertyChanged es que la encadena con una colección observable extendida . Así que viertes tus objetos near poco en una colección y escuchas la colección ... si algo cambia, en cualquier lugar, aprendes sobre ello.
Seré honesto, esto podría unirse a la discusión "¿Por qué INotifyPropertyChanged no fue manejado automáticamente por el compilador?", Que depende de: Cada objeto en c # debería tener la capacidad de notificar si alguna parte de él fue cambiada; es decir, implementar INotifyPropertyChanged por defecto. Pero no es así y la mejor ruta, que requiere la menor cantidad de esfuerzo, es usar IL Weaving (específicamente FODY ).
fuente
Hilo bastante antiguo, pero después de muchas búsquedas se me ocurrió mi propia solución: A PropertyChangedProxy
Con esta clase, puede registrarse fácilmente en NotifyPropertyChanged de otra persona y tomar las medidas adecuadas si se despide de la propiedad registrada.
Aquí hay una muestra de cómo podría verse esto cuando tiene una propiedad de modelo "Estado" que puede cambiar por sí misma y luego debe notificar automáticamente a ViewModel que active su propia PropertyChanged en su propiedad "Estado" para que la vista también sea notificada: )
y aquí está la clase en sí:
fuente
-= my_event_handler
), porque es más fácil de rastrear que un problema zombie raro e impredecible que puede o no suceder nunca.Encontré este artículo útil: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf
Mi resumen:
La idea detrás de la organización MVVM es permitir una reutilización más fácil de vistas y modelos y también permitir pruebas desacopladas. Su modelo de vista es un modelo que representa las entidades de vista, su modelo representa las entidades comerciales.
¿Y si quisieras hacer un juego de póquer más tarde? Gran parte de la interfaz de usuario debería ser reutilizable. Si la lógica de su juego está ligada a su modelo de vista, sería muy difícil reutilizar esos elementos sin tener que reprogramar el modelo de vista. ¿Qué pasa si desea cambiar su interfaz de usuario? Si la lógica de su juego está acoplada a la lógica de su modelo de vista, deberá volver a verificar que su juego aún funcione. ¿Qué sucede si desea crear un escritorio y una aplicación web? Si su modelo de vista contiene la lógica del juego, sería complicado tratar de mantener estas dos aplicaciones una al lado de la otra, ya que la lógica de la aplicación estaría inevitablemente ligada a la lógica empresarial en el modelo de vista.
Las notificaciones de cambio de datos y la validación de datos ocurren en cada capa (la vista, el modelo de vista y el modelo).
El modelo contiene sus representaciones de datos (entidades) y lógica empresarial específica para esas entidades. Una baraja de cartas es una "cosa" lógica con propiedades inherentes. Un buen mazo no puede tener cartas duplicadas. Necesita exponer una forma de obtener la (s) tarjeta (s) superior (s). Necesita saber que no debe entregar más tarjetas de las que le quedan. Estos comportamientos de la baraja son parte del modelo porque son inherentes a una baraja de cartas. También habrá modelos de distribuidores, modelos de jugadores, modelos de manos, etc. Estos modelos pueden interactuar y lo harán.
El modelo de vista consistiría en la presentación y la lógica de aplicación. Todo el trabajo asociado con la visualización del juego está separado de la lógica del juego. Esto podría incluir mostrar las manos como imágenes, solicitudes de tarjetas para el modelo del distribuidor, configuraciones de visualización del usuario, etc.
Las entrañas del artículo:
fuente
La notificación basada en INotifyPropertyChanged e INotifyCollectionChanged es exactamente lo que necesita. Para simplificar su vida con la suscripción a los cambios de propiedad, la validación en tiempo de compilación del nombre de la propiedad, evitando pérdidas de memoria, le aconsejo que utilice PropertyObserver de la Fundación MVVM de Josh Smith . Como este proyecto es de código abierto, puede agregar solo esa clase a su proyecto desde las fuentes.
Para comprender cómo usar PropertyObserver, lea este artículo .
Además, eche un vistazo más profundo a Reactive Extensions (Rx) . Puede exponer IObserver <T> de su modelo y suscribirse a él en el modelo de vista.
fuente
Los chicos hicieron un trabajo increíble respondiendo esto, pero en situaciones como esta realmente siento que el patrón MVVM es un dolor, así que iría y usaría un controlador de supervisión o un enfoque de vista pasiva y soltaría el sistema de enlace al menos para los objetos modelo que generan cambios por sí mismos.
fuente
He estado defendiendo el modelo direccional -> Ver modelo -> Ver el flujo de cambios durante mucho tiempo, como puede ver en la sección Flujo de cambios de mi artículo MVVM de 2008. Esto requiere implementarlo
INotifyPropertyChanged
en el modelo. Por lo que puedo decir, desde entonces se ha convertido en una práctica común.Debido a que mencionó a Josh Smith, eche un vistazo a su clase PropertyChanged . Es una clase de ayuda para suscribirse al
INotifyPropertyChanged.PropertyChanged
evento de la modelo .De hecho, puede llevar este enfoque mucho más allá, ya que recientemente lo hice al crear mi clase PropertiesUpdater . Las propiedades del modelo de vista se calculan como expresiones complejas que incluyen una o más propiedades en el modelo.
fuente
No hay nada de malo en implementar INotifyPropertyChanged dentro de Model y escucharlo dentro de ViewModel. De hecho, incluso puede introducir puntos en el derecho de propiedad del modelo en XAML: {Binding Model.ModelProperty}
En cuanto a las propiedades de solo lectura dependientes / calculadas, no he visto nada mejor y más simple que esto: https://github.com/StephenCleary/CalculatedProperties . Es muy simple pero increíblemente útil, es realmente "fórmulas de Excel para MVVM" - simplemente funciona de la misma manera que Excel propagando cambios a las celdas de fórmula sin esfuerzo adicional de su parte.
fuente
Puede generar eventos desde el modelo, a los que el modelo de vista necesitaría suscribirse.
Por ejemplo, recientemente trabajé en un proyecto para el que tuve que generar una vista de árbol (naturalmente, el modelo tenía una naturaleza jerárquica). En el modelo tenía una colección observable llamada
ChildElements
.En el modelo de vista, había almacenado una referencia al objeto en el modelo y me suscribí al
CollectionChanged
evento de la colección observable, así:ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)
...Luego, su modelo de vista se notifica automáticamente una vez que ocurre un cambio en el modelo. Puede seguir el mismo concepto utilizando
PropertyChanged
, pero deberá generar explícitamente eventos de cambio de propiedad de su modelo para que eso funcione.fuente
Esto me parece una pregunta realmente importante, incluso cuando no hay presión para hacerlo. Estoy trabajando en un proyecto de prueba, que involucra un TreeView. Hay elementos de menú y otros que se asignan a comandos, por ejemplo, Eliminar. Actualmente, estoy actualizando tanto el modelo como el modelo de vista desde dentro del modelo de vista.
Por ejemplo,
Esto es simple, pero parece tener un defecto muy básico. Una prueba unitaria típica ejecutaría el comando y luego verificaría el resultado en el modelo de vista. Pero esto no prueba que la actualización del modelo fuera correcta, ya que los dos se actualizan simultáneamente.
Entonces, tal vez sea mejor usar técnicas como PropertyObserver para permitir que la actualización del modelo active una actualización del modelo de vista. La misma prueba unitaria ahora solo funcionaría si ambas acciones tuvieran éxito.
Esta no es una respuesta potencial, me doy cuenta, pero parece que vale la pena publicarla.
fuente