MVVM en WPF - ¿Cómo alertar a ViewModel de cambios en el modelo ... o debería?


112

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 INotifyPropertyChangedy 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, Decksino también la BlackJackGameclase 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 BlackJackGameexpongo métodos como "DrawCard" y se me ocurrió que cuando se saca una tarjeta, las propiedades como CardScore, y IsBustdeben 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?


3
La interacción que está describiendo suena como un mecanismo de evento estándar es todo lo que necesita. El modelo puede exponer un evento llamado OnBusty la máquina virtual puede suscribirse a él. Supongo que también podría utilizar un enfoque de la IEA.
code4life

Seré honesto, si tuviera que hacer una 'aplicación' de blackjack real, mis datos se ocultarían detrás de unas pocas capas de servicios / proxies y un nivel pedante de pruebas unitarias similares a A + B = C. Sería el proxy / servicio que informa de cambios.
Meirion Hughes

1
¡Gracias a todos! Desafortunadamente, solo puedo elegir una respuesta. Elijo el de Rachel debido a los consejos adicionales de arquitectura y la limpieza de la pregunta original. Pero hubo muchas respuestas excelentes y las aprecio. -Dave
Dave


2
FWIW: Después de luchar durante varios años con las complejidades de mantener tanto VM como M por concepto de dominio, ahora creo que tener ambos fracasa en DRY; la separación necesaria de preocupaciones se puede hacer más fácilmente al tener dos INTERFACES en un solo objeto: una "Interfaz de dominio" y una "Interfaz ViewModel". Este objeto se puede pasar tanto a la lógica empresarial como a la lógica de la vista, sin confusión ni falta de sincronización. Ese objeto es un "objeto de identidad": representa de forma única a la entidad. Mantener la separación entre el código de dominio y el código de vista necesita mejores herramientas para hacerlo dentro de una clase.
ToolmakerSteve

Respuestas:


61

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í:

// Attach EventHandler
PlayerModel.PropertyChanged += PlayerModel_PropertyChanged;

...

// When property gets changed in the Model, raise the PropertyChanged 
// event of the ViewModel copy of the property
PlayerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SomeProperty")
        RaisePropertyChanged("ViewModelCopyOfSomeProperty");
}

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 EventAggregatoro MVVM Light Messenger.

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 PlayerScoreHasChangedMessagedesde un objeto y otro objeto puede suscribirse para escuchar esos tipos de mensajes y actualizar su PlayerScorepropiedad 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:

class CardModel
{
    int Score;
    SuitEnum Suit;
    CardEnum CardValue;
}

class PlayerModel 
{
    ObservableCollection<Card> FaceUpCards;
    ObservableCollection<Card> FaceDownCards;
    int CurrentScore;

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}

y tendrías un objeto ViewModel como

public class GameViewModel
{
    ObservableCollection<CardModel> Deck;
    PlayerModel Dealer;
    PlayerModel Player;

    ICommand DrawCardCommand;

    void DrawCard(Player currentPlayer)
    {
        var nextCard = Deck.First();
        currentPlayer.FaceUpCards.Add(nextCard);

        if (currentPlayer.IsBust)
            // Process next player turn

        Deck.Remove(nextCard);
    }
}

(Los objetos anteriores deberían implementarse todos INotifyPropertyChanged, pero lo dejé fuera por simplicidad)


3
De manera más general, ¿todas las reglas / lógica de negocios van en el modelo? ¿A dónde va toda la lógica que dice que puedes tomar una carta hasta 21 (pero el crupier se queda en 17), que puedes dividir cartas, etc. Asumí que todo pertenecía a la clase de modelo y por esa razón sentí que necesitaba una clase de controlador BlacJackGame en el modelo. Todavía estoy tratando de entender esto y agradecería ejemplos / referencias. La idea de blackjack, por ejemplo, se tomó de una clase de iTunes en la programación de iOS donde la lógica / reglas de negocios está definitivamente en la clase de modelo de un patrón MVC.
Dave

3
@Dave Sí, el 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.
Rachel

6
Gracias Rachel por toda la ayuda. Tendré que investigar esto un poco más o escribir otra pregunta; Todavía estoy confundido sobre la ubicación de la lógica del juego. Usted (y otros) abogan por ponerlo en ViewModel, otros dicen "lógica empresarial", que en mi caso supongo que las reglas del juego y el estado del juego pertenecen al modelo (consulte, por ejemplo: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) y stackoverflow.com/questions/10964003/… ). Reconozco que en este sencillo juego probablemente no importe mucho. Pero sería bueno saberlo. ¡Gracias!
Dave

1
@ Dave Es posible que esté usando el término "lógica de negocios" incorrectamente y mezclándolo con la lógica de la aplicación. Para citar el artículo de MSDN que vinculó, "Para maximizar las oportunidades de reutilización, los modelos no deben contener ningún comportamiento o lógica de aplicación específicos de casos de uso o tareas del usuario" y "Normalmente, el modelo de vista definirá comandos o acciones que se pueden representar en la interfaz de usuario y que el usuario puede invocar " . Entonces, cosas como a DrawCardCommand()estarían en ViewModel, pero supongo que podría tener un BlackjackGameModelobjeto que contuviera un DrawCard()método que el comando llamara si quisiera
Rachel

2
Evite pérdidas de memoria. Utilice un patrón WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

24

Respuesta 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:

  1. Se crea el modelo de vista y se envuelve el modelo
  2. Viewmodel se suscribe al PropertyChangedevento de la modelo
  3. Viewmodel se establece como vista DataContext, las propiedades están vinculadas, etc.
  4. Ver desencadena la acción en el modelo de vista
  5. Viewmodel llama al método en el modelo
  6. El modelo se actualiza solo
  7. Viewmodel maneja el modelo PropertyChangedy levanta el suyo PropertyChangeden respuesta
  8. La vista refleja los cambios en sus enlaces, cerrando el ciclo de retroalimentación

Por 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í .


Hola, la lista que has hecho es brillante. Sin embargo, tengo un problema con 7. y 8. En particular: tengo un ViewModel, que no implementa INotifyPropertyChanged. Contiene una lista de elementos secundarios, que contiene una lista de elementos secundarios (se usa como ViewModel para un control WPF Treeview). ¿Cómo hago para que UserControl DataContext ViewModel "escuche" los cambios de propiedad en cualquiera de los elementos secundarios (TreeviewItems)? ¿Cómo me suscribo exactamente a todos los elementos secundarios, que implementan INotifyPropertyChanged? ¿O debería hacer una pregunta aparte?
Igor

4

Tus opciones:

  • Implementar INotifyPropertyChanged
  • Eventos
  • POCO con manipulador Proxy

Como yo lo veo, INotifyPropertyChangedes parte fundamental de .Net. es decir, está en System.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):

public class NearlyPOCO: INotifyPropertyChanged
{
     public string ValueA {get;set;}
     public string ValueB {get;set;}

     public event PropertyChangedEventHandler PropertyChanged;
}

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 ).


4

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: )

public class MyModel : INotifyPropertyChanged
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set { _status = value; OnPropertyChanged(); }
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    public string Status
    {
        get { return _model.Status; }
    }

    private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
    private MyModel _model;
    public MyViewModel(MyModel model)
    {
        _model = model;
        _statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
            _model, myModel => myModel.Status, s => OnPropertyChanged("Status")
        );
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

y aquí está la clase en sí:

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
    private readonly Func<TSource, TPropType> _getValueFunc;
    private readonly TSource _source;
    private readonly Action<TPropType> _onPropertyChanged;
    private readonly string _modelPropertyname;

    /// <summary>
    /// Constructor for a property changed proxy
    /// </summary>
    /// <param name="source">The source object to listen for property changes</param>
    /// <param name="selectorExpression">Expression to the property of the source</param>
    /// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
    public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
    {
        _source = source;
        _onPropertyChanged = onPropertyChanged;
        // Property "getter" to get the value
        _getValueFunc = selectorExpression.Compile();
        // Name of the property
        var body = (MemberExpression)selectorExpression.Body;
        _modelPropertyname = body.Member.Name;
        // Changed event
        _source.PropertyChanged += SourcePropertyChanged;
    }

    private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _modelPropertyname)
        {
            _onPropertyChanged(_getValueFunc(_source));
        }
    }
}

1
Evite pérdidas de memoria. Utilice un patrón WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

1
@JJS - OTOH, considere que el patrón de eventos débil es peligroso . Personalmente, prefiero arriesgarme a una pérdida de memoria si me olvido de anular el registro ( -= my_event_handler), porque es más fácil de rastrear que un problema zombie raro e impredecible que puede o no suceder nunca.
ToolmakerSteve

@ToolmakerSteve gracias por agregar un argumento equilibrado. Sugiero que los desarrolladores hagan lo que sea mejor para ellos, en su propia situación. No adopte ciegamente el código fuente de Internet. Hay otros patrones como el EventAggregator / EventBus de mensajería de componentes cruzados de uso común (que también se generan con sus propios peligros)
JJS

2

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:

Básicamente, la forma en que me gusta explicar esto es que su lógica empresarial y sus entidades comprenden el modelo. Esto es lo que está usando su aplicación específica, pero podría compartirse entre muchas aplicaciones.

La Vista es la capa de presentación, cualquier cosa relacionada con la interacción directa con el usuario.

ViewModel es básicamente el "pegamento" específico de su aplicación que une a los dos.

Tengo un bonito diagrama aquí que muestra cómo interactúan:

http://reedcopsey.com/2010/01/06/better-user-and-developer-experiences-from-windows-forms-to-wpf-with-mvvm-part-7-mvvm/

En su caso, abordemos algunos de los detalles ...

Validación: esto suele presentarse en 2 formas. La validación relacionada con la entrada del usuario ocurriría en el ViewModel (principalmente) y en la Vista (es decir, el TextBox "numérico" que evita que se ingrese texto se maneja por usted en la vista, etc.). Como tal, la validación de la entrada del usuario suele ser una preocupación de la VM. Dicho esto, a menudo hay una segunda "capa" de validación: esta es la validación de que los datos que se utilizan coinciden con las reglas comerciales. A menudo, esto es parte del modelo en sí; cuando inserta datos en su modelo, puede causar errores de validación. Luego, la VM deberá reasignar esta información a la Vista.

Operaciones "detrás de escena sin vista, como escribir en la base de datos, enviar correo electrónico, etc.": esto es realmente parte de las "Operaciones específicas del dominio" en mi diagrama, y ​​en realidad es puramente parte del Modelo. Esto es lo que intentas exponer a través de la aplicación. ViewModel actúa como un puente para exponer esta información, pero las operaciones son de modelo puro.

Operaciones para ViewModel: ViewModel necesita más que solo INPC; también necesita cualquier operación que sea específica para su aplicación (no su lógica empresarial), como guardar preferencias y estado del usuario, etc. Esto va a variar de aplicación. por aplicación, incluso cuando se interconecta el mismo "modelo".

Una buena forma de pensarlo: digamos que desea hacer 2 versiones de su sistema de pedidos. El primero está en WPF y el segundo es una interfaz web.

La lógica compartida que se ocupa de los pedidos en sí (envío de correos electrónicos, entrada en DB, etc.) es el Modelo. Su aplicación está exponiendo estas operaciones y datos al usuario, pero lo hace de 2 formas.

En la aplicación WPF, la interfaz de usuario (con lo que interactúa el espectador) es la "vista"; en la aplicación web, este es básicamente el código que (al menos eventualmente) se convierte en javascript + html + css en el cliente.

ViewModel es el resto del "pegamento" que se requiere para adaptar su modelo (estas operaciones relacionadas con el pedido) para que funcione con la tecnología / capa de vista específica que está utilizando.


Quizás un ejemplo simple es un reproductor de música. Sus modelos contendrían las bibliotecas y el archivo de sonido activo y los códecs y la lógica del reproductor y el código de procesamiento de señales digitales. Los modelos de vista contendrían sus controles y visualizaciones y el navegador de la biblioteca. se necesita mucha lógica de la interfaz de usuario para mostrar toda esa información y sería bueno permitir que un programador se concentre en hacer que la música se reproduzca mientras que otro programador se concentrará en hacer que la interfaz de usuario sea intuitiva y divertida. El modelo de vista y el modelo deberían permitir a esos dos programadores ponerse de acuerdo sobre un conjunto de interfaces y trabajar por separado.
VoteCoffee

Otro buen ejemplo es una página web. La lógica del lado del servidor es generalmente equivalente a un modelo. La lógica del lado del cliente es generalmente equivalente a un modelo de vista. Me imagino fácilmente que la lógica del juego pertenecería al servidor y no sería confiada al cliente.
VoteCoffee

2

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.


¡Muchas gracias por hacer referencia al excelente artículo de Josh Smith y cubrir los eventos débiles!
JJS

1

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.


1

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 INotifyPropertyChangeden 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.PropertyChangedevento 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.


1

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.


0

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 CollectionChangedevento 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.


Si se trata de datos jerárquicos, querrá ver la Demo 2 de mi artículo MVVM .
HappyNomad

0

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,

public void DeleteItemExecute ()
{
    DesignObjectViewModel node = this.SelectedNode;    // Action is on selected item
    DocStructureManagement.DeleteNode(node.DesignObject); // Remove from application
    node.Remove();                                // Remove from view model
    Controller.UpdateDocument();                  // Signal document has changed
}

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.