MVVM in WPF - Come avvisare ViewModel delle modifiche nel modello ... o dovrei?


112

Sto esaminando alcuni articoli MVVM, principalmente questo e questo .

La mia domanda specifica è: come comunico le modifiche al modello dal modello al ViewModel?

Nell'articolo di Josh, non vedo che lo faccia. Il ViewModel chiede sempre le proprietà al modello. Nell'esempio di Rachel, ha l'implementazione del modello INotifyPropertyChangede genera eventi dal modello, ma sono a disposizione della vista stessa (vedere il suo articolo / codice per maggiori dettagli sul motivo per cui lo fa).

Da nessuna parte vedo esempi in cui il modello avvisa il ViewModel delle modifiche alle proprietà del modello. Questo mi ha preoccupato che forse non è stato fatto per qualche motivo. Esiste un modello per avvisare il ViewModel delle modifiche nel modello? Sembrerebbe essere necessario poiché (1) plausibilmente ci sono più di 1 ViewModel per ogni modello e (2) anche se c'è un solo ViewModel, alcune azioni sul modello potrebbero comportare la modifica di altre proprietà.

Ho il sospetto che potrebbero esserci risposte / commenti del modulo "Perché vorresti farlo?" commenti, quindi ecco una descrizione del mio programma. Sono nuovo in MVVM quindi forse il mio intero progetto è difettoso. Lo descriverò brevemente.

Sto programmando qualcosa che è più interessante (almeno per me!) Delle classi "Cliente" o "Prodotto". Sto programmando il BlackJack.

Ho una vista che non ha alcun codice dietro e si basa solo sull'associazione a proprietà e comandi nel ViewModel (vedere l'articolo di Josh Smith).

Nel bene e nel male, ho assunto l'atteggiamento che il Modello dovrebbe contenere non solo classi come PlayingCard, Deckma anche la BlackJackGameclasse che mantiene lo stato dell'intero gioco e sa quando il giocatore è andato in rovina, il banco deve pescare le carte e qual è il punteggio attuale del giocatore e del banco (meno di 21, 21, sballo, ecc.).

Da BlackJackGameespongo metodi come "DrawCard" e mi è venuto in mente che quando viene estratta una carta, proprietà come CardScoree IsBustdovrebbero essere aggiornate e questi nuovi valori comunicati al ViewModel. Forse è un pensiero sbagliato?

Si potrebbe assumere l'atteggiamento che ViewModel abbia chiamato il DrawCard()metodo, quindi dovrebbe sapere di chiedere un punteggio aggiornato e scoprire se è fallito o meno. Opinioni?

Nel mio ViewModel, ho la logica per prendere un'immagine reale di una carta da gioco (basata su seme, grado) e renderla disponibile per la visualizzazione. Il modello non dovrebbe preoccuparsi di questo (forse altri ViewModel userebbero solo i numeri invece delle immagini delle carte da gioco). Certo, forse qualcuno mi dirà che il Model non dovrebbe nemmeno avere il concetto di un gioco di BlackJack e che dovrebbe essere gestito nel ViewModel?


3
L'interazione che stai descrivendo suona come un meccanismo di eventi standard è tutto ciò di cui hai bisogno. Il modello può esporre un evento chiamato OnBuste la VM può sottoscriverlo. Immagino che potresti anche utilizzare un approccio IEA.
code4life

Sarò onesto, se dovessi creare una vera `` app '' di blackjack i miei dati sarebbero nascosti dietro alcuni livelli di servizi / proxy e un livello pedante di unit test simile ad A + B = C.Sarebbe il proxy / servizio che informa delle modifiche.
Meirion Hughes

1
Grazie a tutti! Purtroppo posso scegliere solo una risposta. Scelgo Rachel per via dei consigli extra sull'architettura e per aver risolto la domanda originale. Ma c'erano molte ottime risposte e le apprezzo. -Dave
Dave


2
FWIW: Dopo aver lottato per diversi anni con la complessità di mantenere sia VM che M per concetto di dominio, ora credo che avere entrambi fallisce DRY; la necessaria separazione dei problemi può essere eseguita più facilmente avendo due INTERFACCE su un singolo oggetto: una "Interfaccia di dominio" e una "Interfaccia ViewModel". Questo oggetto può essere passato sia alla logica aziendale che alla logica di visualizzazione, senza confusione o mancanza di sincronizzazione. Quell'oggetto è un "oggetto identità" - rappresenta in modo univoco l'entità. Mantenere la separazione tra codice di dominio e codice di visualizzazione richiede quindi strumenti migliori per farlo all'interno di una classe.
ToolmakerSteve

Risposte:


61

Se vuoi che i tuoi modelli avvisino i ViewModels delle modifiche, dovrebbero implementare INotifyPropertyChanged e i ViewModels dovrebbero iscriversi per ricevere le notifiche di PropertyChange.

Il tuo codice potrebbe essere simile a questo:

// 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");
}

Ma in genere questo è necessario solo se più di un oggetto apporterà modifiche ai dati del modello, il che di solito non è il caso.

Se hai un caso in cui non hai effettivamente un riferimento alla tua proprietà Model per allegare ad esso l'evento PropertyChanged, puoi utilizzare un sistema di messaggistica come Prism EventAggregatoro MVVM Light Messenger.

Ho una breve panoramica dei sistemi di messaggistica sul mio blog, tuttavia per riassumerla, qualsiasi oggetto può trasmettere un messaggio e qualsiasi oggetto può iscriversi per ascoltare messaggi specifici. Quindi potresti trasmettere un messaggio PlayerScoreHasChangedMessageda un oggetto e un altro oggetto può iscriversi per ascoltare quei tipi di messaggi e aggiornare la sua PlayerScoreproprietà quando ne sente uno.

Ma non credo che questo sia necessario per il sistema che hai descritto.

In un mondo MVVM ideale, la tua applicazione è composta dai tuoi ViewModel ei tuoi modelli sono gli unici blocchi usati per costruire la tua applicazione. In genere contengono solo dati, quindi non avrebbero metodi come DrawCard()(che sarebbe in un ViewModel)

Quindi probabilmente avresti semplici oggetti dati del modello come questi:

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

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

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

e avresti un oggetto ViewModel come

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);
    }
}

(Gli oggetti sopra dovrebbero essere tutti implementati INotifyPropertyChanged, ma l'ho lasciato fuori per semplicità)


3
Più in generale, tutte le logiche / regole di business vanno nel modello? Dove va a finire tutta la logica che dice che puoi prendere una carta fino a 21 (ma il mazziere rimane a 17), che puoi dividere le carte, ecc. Ho pensato che appartenesse tutto alla classe del modello e per questo motivo ho sentito di aver bisogno una classe controller BlacJackGame nel modello. Sto ancora cercando di capirlo e apprezzerei esempi / riferimenti. L'idea del blackjack per un esempio è stata presa da una classe iTunes sulla programmazione iOS in cui la logica / le regole di business sono decisamente nella classe modello di un pattern MVC.
Dave

3
@ Dave Sì, il DrawCard()metodo sarebbe nel ViewModel, insieme all'altra logica di gioco. In un'applicazione MVVM ideale, dovresti essere in grado di eseguire la tua applicazione senza l'interfaccia utente, semplicemente creando ViewModels ed eseguendo i loro metodi, ad esempio tramite uno script di test o una finestra del prompt dei comandi. I modelli sono in genere solo modelli di dati contenenti dati grezzi e convalida dei dati di base.
Rachel

6
Grazie Rachel per tutto l'aiuto. Dovrò ricercarlo ancora o scrivere un'altra domanda; Sono ancora confuso sulla posizione della logica di gioco. Tu (e altri) sostenete di inserirlo nel ViewModel, altri dicono "logica aziendale" che nel mio caso presumo siano le regole del gioco e lo stato del gioco appartengono al modello (vedere ad esempio: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) e stackoverflow.com/questions/10964003/... ). Riconosco che in questo semplice gioco, probabilmente non importa molto. Ma sarebbe bello saperlo. Thxs!
Dave

1
@ Dave potrei usare il termine "logica aziendale" in modo errato e mescolarlo con la logica dell'applicazione. Per citare l'articolo MSDN che hai collegato "Per massimizzare le opportunità di riutilizzo, i modelli non devono contenere alcun comportamento o logica dell'applicazione specifici per casi d'uso o attività utente" e "In genere, il modello di visualizzazione definirà comandi o azioni che possono essere rappresentati nell'interfaccia utente e che l'utente può richiamare " . Quindi cose come a DrawCardCommand()sarebbero nel ViewModel, ma immagino che potresti avere un BlackjackGameModeloggetto che conteneva un DrawCard()metodo che il comando ha chiamato se lo desideri
Rachel

2
Evita perdite di memoria. Usa un pattern WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

24

Risposta breve: dipende dalle specifiche.

Nel tuo esempio i modelli vengono aggiornati "da soli" e queste modifiche devono ovviamente propagarsi in qualche modo alle viste. Poiché le viste possono accedere direttamente solo ai viewmodel, significa che il modello deve comunicare queste modifiche al viewmodel corrispondente. Il meccanismo stabilito per farlo è ovviamente INotifyPropertyChanged, il che significa che otterrai un flusso di lavoro come questo:

  1. Viewmodel viene creato e avvolge il modello
  2. Viewmodel si iscrive PropertyChangedall'evento del modello
  3. Viewmodel è impostato come view DataContext, le proprietà sono associate ecc
  4. Visualizza attiva l'azione su viewmodel
  5. Viewmodel chiama il metodo sul modello
  6. Il modello si aggiorna da solo
  7. Viewmodel gestisce i modelli PropertyChangede ne aumenta i propri PropertyChangedin risposta
  8. La vista riflette le modifiche nelle sue associazioni, chiudendo il ciclo di feedback

D'altra parte, se i tuoi modelli contenevano poca (o nessuna) logica di business, o se per qualche altro motivo (come l'acquisizione di capacità transazionali) hai deciso di lasciare che ogni viewmodel "possieda" il suo modello avvolto, tutte le modifiche al modello passerebbero attraverso il viewmodel quindi una tale disposizione non sarebbe necessaria.

Descrivo un tale progetto in un'altra domanda MVVM qui .


Ciao, la lista che hai fatto è geniale. Ho comunque un problema con 7. e 8. In particolare: ho un ViewModel, che non implementa INotifyPropertyChanged. Contiene un elenco di figli, che contiene un elenco di figli stesso (viene utilizzato come ViewModel per un controllo Treeview WPF). Come faccio a fare in modo che UserControl DataContext ViewModel "ascolti" le modifiche alle proprietà in uno qualsiasi dei figli (TreeviewItems)? Come mi iscrivo esattamente a tutti gli elementi figlio, che implementano INotifyPropertyChanged? O dovrei fare una domanda separata?
Igor

4

Le tue scelte:

  • Implementare INotifyPropertyChanged
  • eventi
  • POCO con manipolatore proxy

Per come la vedo io, INotifyPropertyChangedè una parte fondamentale di .Net. cioè è dentro System.dll. L'implementazione nel tuo "Modello" è simile all'implementazione di una struttura di eventi.

Se vuoi POCO puro, devi effettivamente manipolare i tuoi oggetti tramite proxy / servizi e quindi il tuo ViewModel viene informato delle modifiche ascoltando il proxy.

Personalmente ho implementato semplicemente INotifyPropertyChanged e poi uso FODY per fare il lavoro sporco per me. Sembra e si sente POCO.

Un esempio (utilizzando FODY per IL Weave the PropertyChanged raisers):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

quindi puoi fare in modo che il tuo ViewModel ascolti PropertyChanged per eventuali modifiche; o modifiche specifiche della proprietà.

La bellezza del percorso INotifyPropertyChanged, è che lo concatenate con un ObservableCollection esteso . Quindi scarichi i tuoi oggetti near poco in una raccolta e ascolti la raccolta ... se qualcosa cambia, ovunque, lo impari.

Sarò onesto, questo potrebbe unirsi alla discussione "Perché INotifyPropertyChanged non è stato gestito automaticamente dal compilatore", che devolve a: Ogni oggetto in c # dovrebbe avere la possibilità di notificare se una parte di esso è stata modificata; cioè implementare INotifyPropertyChanged per impostazione predefinita. Ma non è così e il percorso migliore, che richiede il minimo sforzo, è usare IL Weaving (in particolare FODY ).


4

Discussione abbastanza vecchia ma dopo molte ricerche ho trovato la mia soluzione: A PropertyChangedProxy

Con questa classe, puoi facilmente registrarti a NotifyPropertyChanged di qualcun altro ed eseguire le azioni appropriate se viene attivato per la proprietà registrata.

Ecco un esempio di come potrebbe apparire quando hai una proprietà del modello "Stato" che può cambiare da sola e quindi dovrebbe notificare automaticamente al ViewModel di attivare il proprio PropertyChanged sulla sua proprietà "Stato" in modo che anche la vista venga notificata: )

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));
    }
}

ed ecco la classe stessa:

/// <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
Evita perdite di memoria. Usa un pattern WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

1
@JJS - OTOH, considera che il pattern di eventi deboli è pericoloso . Personalmente preferisco rischiare la perdita di memoria se dimentico di unregister ( -= my_event_handler), perché è più facile da rintracciare rispetto a un raro e imprevedibile problema di zombi che potrebbe o non potrebbe mai accadere.
ToolmakerSteve

@ToolmakerSteve grazie per aver aggiunto un argomento equilibrato. Suggerisco agli sviluppatori di fare ciò che è meglio per loro, nella loro situazione. Non adottare ciecamente codice sorgente da Internet. Ci sono altri modelli come la messaggistica cross-component di EventAggregator / EventBus comunemente usati (che sono anche
creati

2

Ho trovato utile questo articolo: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = WPF

Il mio riassunto:

L'idea alla base dell'organizzazione MVVM è quella di consentire un più facile riutilizzo di viste e modelli e anche di consentire test disaccoppiati. Il tuo modello di visualizzazione è un modello che rappresenta le entità di visualizzazione, il tuo modello rappresenta le entità di business.

E se volessi fare una partita di poker più tardi? Gran parte dell'interfaccia utente dovrebbe essere riutilizzabile. Se la logica di gioco è vincolata al modello di visualizzazione, sarebbe molto difficile riutilizzare quegli elementi senza dover riprogrammare il modello di visualizzazione. E se volessi cambiare la tua interfaccia utente? Se la logica di gioco è accoppiata alla logica del modello di visualizzazione, è necessario ricontrollare che il gioco funzioni ancora. E se volessi creare un desktop e un'app web? Se il tuo modello di visualizzazione contiene la logica del gioco, diventerebbe complicato cercare di mantenere queste due applicazioni fianco a fianco, poiché la logica dell'applicazione sarebbe inevitabilmente collegata alla logica di business nel modello di visualizzazione.

Le notifiche di modifica dei dati e la convalida dei dati avvengono in ogni livello (la vista, il modello di visualizzazione e il modello).

Il modello contiene le rappresentazioni dei dati (entità) e la logica aziendale specifica per tali entità. Un mazzo di carte è una "cosa" logica con proprietà intrinseche. Un buon mazzo non può contenere carte duplicate. Ha bisogno di esporre un modo per ottenere le carte in cima. Ha bisogno di sapere di non distribuire più carte di quante ne abbia. Tali comportamenti del mazzo fanno parte del modello perché sono inerenti a un mazzo di carte. Ci saranno anche modelli di dealer, modelli di giocatori, modelli di mani, ecc. Questi modelli possono interagire e interagiranno.

Il modello di visualizzazione consisterebbe nella presentazione e nella logica dell'applicazione. Tutto il lavoro associato alla visualizzazione del gioco è separato dalla logica del gioco. Ciò potrebbe includere la visualizzazione delle mani come immagini, richieste di carte al modello del rivenditore, impostazioni di visualizzazione dell'utente, ecc.

Il coraggio dell'articolo:

Fondamentalmente, il modo in cui mi piace spiegarlo è che la tua logica di business e le tue entità costituiscono il modello. Questo è ciò che utilizza la tua applicazione specifica, ma potrebbe essere condiviso tra molte applicazioni.

La vista è il livello di presentazione - tutto ciò che riguarda effettivamente l'interfacciamento diretto con l'utente.

Il ViewModel è fondamentalmente il "collante" specifico per la tua applicazione che collega i due insieme.

Ho un bel diagramma qui che mostra come si interfacciano:

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

Nel tuo caso, affrontiamo alcune delle specifiche ...

Convalida: in genere si presenta in 2 forme. La convalida relativa all'input dell'utente avverrebbe nel ViewModel (principalmente) e nella View (ad esempio: TextBox "numerico" che impedisce l'immissione di testo viene gestito per te nella vista, ecc.). In quanto tale, la convalida dell'input da parte dell'utente è in genere un problema della VM. Detto questo, spesso c'è un secondo "livello" di convalida: questa è la convalida che i dati utilizzati corrispondono alle regole aziendali. Questo spesso fa parte del modello stesso: quando invii dati al tuo modello, potrebbero verificarsi errori di convalida. La VM dovrà quindi rimappare queste informazioni nella vista.

Operazioni "dietro le quinte senza vista, come scrivere su DB, inviare e-mail, ecc.": Questo fa davvero parte delle "Operazioni specifiche del dominio" nel mio diagramma ed è in realtà puramente parte del Modello. Questo è ciò che stai cercando di esporre tramite l'applicazione. Il ViewModel funge da bridge per esporre queste informazioni, ma le operazioni sono pure Model.

Operazioni per ViewModel: ViewModel ha bisogno di più di un semplice INPC: necessita anche di qualsiasi operazione specifica per la tua applicazione (non la tua logica aziendale), come il salvataggio delle preferenze e dello stato dell'utente, ecc. per app., anche quando si interfaccia lo stesso "modello".

Un buon modo per pensarci: supponi di voler creare 2 versioni del tuo sistema di ordinazione. Il primo è in WPF e il secondo è un'interfaccia web.

La logica condivisa che si occupa degli ordini stessi (invio di email, entrata in DB, ecc.) È il Modello. La tua applicazione espone queste operazioni e dati all'utente, ma lo fa in 2 modi.

Nell'applicazione WPF, l'interfaccia utente (con cui interagisce il visualizzatore) è la "vista" - nell'applicazione web, questo è fondamentalmente il codice che (almeno alla fine) viene trasformato in javascript + html + css sul client.

Il ViewModel è il resto del "collante" necessario per adattare il tuo modello (queste operazioni relative all'ordinamento) in modo da farlo funzionare con la specifica tecnologia / livello di visualizzazione che stai utilizzando.


Forse un semplice esempio è un lettore musicale. I modelli conterrebbero le librerie, i file audio e i codec attivi, la logica del lettore e il codice di elaborazione del segnale digitale. I modelli di visualizzazione conterrebbero i controlli e le visualizzazioni e il browser della libreria. è necessaria molta logica dell'interfaccia utente per visualizzare tutte queste informazioni e sarebbe bello consentire a un programmatore di concentrarsi sulla riproduzione della musica consentendo a un altro programmatore di concentrarsi sul rendere l'interfaccia utente intuitiva e divertente. View-model e model dovrebbero consentire a questi due programmatori di concordare una serie di interfacce e lavorare separatamente.
Votazione Coffee

Un altro buon esempio è una pagina web. La logica lato server è generalmente equivalente a un modello. La logica lato client è generalmente equivalente a un modello di visualizzazione. Immagino facilmente che la logica del gioco appartenga al server e non venga affidata al client.
Votazione Coffee

2

La notifica basata su INotifyPropertyChanged e INotifyCollectionChanged è esattamente ciò di cui hai bisogno. Per semplificare la tua vita con abbonamento a cambiamenti di proprietà, la convalida in fase di compilazione del nome della proprietà, evitando perdite di memoria, Vi consiglio di utilizzare PropertyObserver dalla Fondazione MVVM di Josh Smith . Poiché questo progetto è open source, puoi aggiungere solo quella classe al tuo progetto dai sorgenti.

Per capire come utilizzare PropertyObserver leggi questo articolo .

Inoltre, dai uno sguardo più approfondito a Reactive Extensions (Rx) . Puoi esporre IObserver <T> dal tuo modello e iscriverti ad esso nel modello di visualizzazione.


Grazie mille per aver fatto riferimento all'eccellente articolo di Josh Smith e per aver coperto gli eventi deboli!
JJS

1

I ragazzi hanno fatto un ottimo lavoro rispondendo a questo, ma in situazioni come questa sento davvero che il pattern MVVM è un dolore, quindi andrei e userei un controller di supervisione o un approccio di visualizzazione passiva e lascerei andare il sistema di associazione almeno per oggetti modello che generano cambiamenti da soli.


1

Sto sostenendo il modello direzionale -> Visualizza modello -> Visualizza flusso di modifiche da molto tempo, come puoi vedere nella sezione Flusso di modifiche del mio articolo MVVM del 2008. Ciò richiede l'implementazione INotifyPropertyChangedsul modello. Per quanto ne so, da allora è diventata pratica comune.

Perché hai menzionato Josh Smith, dai un'occhiata alla sua classe PropertyChanged . È una classe di supporto per iscriversi INotifyPropertyChanged.PropertyChangedall'evento del modello .

Puoi effettivamente portare questo approccio molto oltre, dato che ho recentemente creato la mia classe PropertiesUpdater . Le proprietà sul modello di visualizzazione vengono calcolate come espressioni complesse che includono una o più proprietà sul modello.


1

Non c'è niente di sbagliato nell'implementare INotifyPropertyChanged all'interno di Model e ascoltarlo all'interno di ViewModel. In effetti puoi persino puntare nella proprietà del modello in XAML: {Binding Model.ModelProperty}

Per quanto riguarda le proprietà di sola lettura dipendenti / calcolate, di gran lunga non ho visto niente di meglio e di più semplice di questo: https://github.com/StephenCleary/CalculatedProperties . È molto semplice ma incredibilmente utile, è davvero "formule di Excel per MVVM" - funziona allo stesso modo di Excel che propaga le modifiche alle celle delle formule senza alcuno sforzo da parte tua.


0

Puoi generare eventi dal modello, a cui il viewmodel dovrebbe iscriversi.

Ad esempio, di recente ho lavorato a un progetto per il quale dovevo generare una visualizzazione ad albero (naturalmente, il modello aveva una natura gerarchica). Nel modello ho chiamato una raccolta osservabile ChildElements.

Nel viewmodel, avevo memorizzato un riferimento all'oggetto nel modello e mi sono iscritto CollectionChangedall'evento della raccolta osservabile, in questo modo: ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

Quindi il tuo viewmodel riceve automaticamente una notifica quando si verifica una modifica nel modello. Puoi seguire lo stesso concetto usando PropertyChanged, ma dovrai aumentare esplicitamente gli eventi di modifica della proprietà dal tuo modello affinché funzioni.


Se hai a che fare con dati gerarchici, ti consigliamo di guardare Demo 2 del mio articolo MVVM .
HappyNomad

0

Questa mi sembra una domanda davvero importante, anche quando non c'è alcuna pressione per farlo. Sto lavorando a un progetto di test, che prevede un TreeView. Ci sono voci di menu e simili che sono mappate ai comandi, ad esempio Elimina. Al momento, sto aggiornando sia il modello che il modello di visualizzazione dall'interno del modello di visualizzazione.

Per esempio,

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
}

Questo è semplice, ma sembra avere un difetto di base. Un tipico unit test eseguirà il comando, quindi verificherebbe il risultato nel modello di visualizzazione. Ma questo non prova che l'aggiornamento del modello fosse corretto, poiché i due vengono aggiornati contemporaneamente.

Quindi forse è meglio usare tecniche come PropertyObserver per consentire all'aggiornamento del modello di attivare un aggiornamento del modello di visualizzazione. Lo stesso test unitario ora funzionerebbe solo se entrambe le azioni avessero successo.

Questa non è una potenziale risposta, me ne rendo conto, ma sembra che valga la pena metterla lì.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.