Buoni esempi di modello MVVM


141

Attualmente sto lavorando con il modello Microsoft MVVM e trovo frustrante la mancanza di esempi dettagliati. L'esempio di ContactBook incluso mostra pochissima gestione dei comandi e l'unico altro esempio che ho trovato proviene da un articolo di MSDN Magazine in cui i concetti sono simili ma utilizza un approccio leggermente diverso e ancora privo di complessità. Esistono esempi MVVM decenti che mostrano almeno le operazioni CRUD di base e il cambio di dialoghi / contenuti?


I suggerimenti di tutti sono stati davvero utili e inizierò a compilare un elenco di buone risorse

Frameworks / Modelli

Articoli utili

screencast

Biblioteche aggiuntive


Sono lieto che queste risorse abbiano aiutato. Sono attualmente alla mia seconda applicazione MVVM di produzione e continuerò ad aggiungere contenuti che saranno utili per coloro che iniziano mentre lo incontro.
gennaio

Risposte:


59

Sfortunatamente non esiste un'eccezionale app di esempio MVVM che fa tutto e ci sono molti approcci diversi nel fare le cose. Innanzitutto, potresti voler familiarizzare con uno dei framework di app (Prism è una scelta decente), perché ti offrono strumenti convenienti come iniezione di dipendenza, comando, aggregazione di eventi, ecc. Per provare facilmente diversi schemi adatti a te .

La versione del prisma:
http://www.codeplex.com/CompositeWPF

Include un'app di esempio abbastanza decente (l'operatore di borsa) insieme a molti esempi più piccoli e come fare. Per lo meno, è una buona dimostrazione di diversi schemi secondari comuni che le persone usano per far funzionare MVVM. Hanno esempi sia per CRUD che per i dialoghi, credo.

Il prisma non è necessariamente per ogni progetto, ma è una buona cosa familiarizzare.

CRUD: questa parte è piuttosto semplice, i collegamenti bidirezionali WPF rendono davvero semplice la modifica della maggior parte dei dati. Il vero trucco è fornire un modello che semplifichi la configurazione dell'interfaccia utente. Per lo meno, vuoi assicurarti che ViewModel (o oggetto business) implementi INotifyPropertyChangedper supportare l'associazione e puoi associare le proprietà direttamente ai controlli dell'interfaccia utente, ma potresti anche voler implementare IDataErrorInfoper la convalida. In genere, se si utilizza una sorta di soluzione ORM, l'impostazione di CRUD è un gioco da ragazzi.

Questo articolo dimostra semplici operazioni crud: http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx

È basato su LinqToSql, ma questo è irrilevante per l'esempio: tutto ciò che è importante è l'implementazione degli oggetti business INotifyPropertyChanged(quali classi generate da LinqToSql). MVVM non è il punto di quell'esempio, ma non credo sia importante in questo caso.

Questo articolo dimostra la convalida dei dati
http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx

Ancora una volta, la maggior parte delle soluzioni ORM genera classi che già implementano IDataErrorInfoe in genere forniscono un meccanismo per facilitare l'aggiunta di regole di convalida personalizzate.

La maggior parte delle volte puoi prendere un oggetto (modello) creato da alcuni ORM e avvolgerlo in un ViewModel che lo contiene e i comandi per salvare / eliminare - e sei pronto per associare l'interfaccia utente direttamente alle proprietà del modello.

La vista sarebbe simile a questa (ViewModel ha una proprietà Itemche contiene il modello, come una classe creata nell'ORM):

<StackPanel>
   <StackPanel DataContext=Item>
      <TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
      <TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
   </StackPanel>
   <Button Command="{Binding SaveCommand}" />
   <Button Command="{Binding CancelCommand}" />
</StackPanel>

Finestre di dialogo: le finestre di dialogo e MVVM sono un po 'complicate. Preferisco usare un sapore dell'approccio Mediatore con le finestre di dialogo, puoi leggere un po 'di più a riguardo in questa domanda StackOverflow:
esempio di finestra di dialogo MVPM WPF

Il mio solito approccio, che non è abbastanza classico MVVM, può essere riassunto come segue:

Una classe di base per una finestra di dialogo ViewModel che espone i comandi per le azioni di commit e di annullamento, un evento che consente alla vista di sapere che una finestra di dialogo è pronta per essere chiusa e qualsiasi altra cosa sia necessaria in tutte le finestre di dialogo.

Una vista generica per la tua finestra di dialogo: può essere una finestra o un controllo personalizzato del tipo di overlay "modale". Fondamentalmente è un presentatore di contenuti in cui scarichiamo il viewmodel e gestisce il cablaggio per chiudere la finestra: ad esempio in caso di modifica del contesto dei dati è possibile verificare se il nuovo ViewModel è ereditato dalla classe di base e, se lo è, iscriversi al relativo evento di chiusura (il gestore assegnerà il risultato della finestra di dialogo). Se si fornisce una funzionalità di chiusura universale alternativa (il pulsante X, ad esempio), è necessario assicurarsi di eseguire anche il comando di chiusura rilevante su ViewModel.

Da qualche parte è necessario fornire modelli di dati per i tuoi ViewModels, che possono essere molto semplici, soprattutto perché probabilmente hai una vista per ogni finestra di dialogo incapsulata in un controllo separato. Il modello di dati predefinito per un ViewModel sarebbe quindi simile al seguente:

<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
   <views:AddressEditView DataContext="{Binding}" />
</DataTemplate>

La vista della finestra di dialogo deve avere accesso a questi, perché altrimenti non saprà come mostrare ViewModel, a parte la UI della finestra di dialogo condivisa i suoi contenuti sono sostanzialmente questi:

<ContentControl Content="{Binding}" />

Il modello di dati implicito mapperà la vista sul modello, ma chi lo avvia?

Questa è la parte non così mvvm. Un modo per farlo è utilizzare un evento globale. Quello che penso sia una cosa migliore da fare è usare un'impostazione del tipo di aggregatore di eventi, fornita attraverso l'iniezione di dipendenza - in questo modo l'evento è globale per un contenitore, non per l'intera app. Prism usa il framework di unità per la semantica del container e l'iniezione di dipendenza, e nel complesso mi piace un po 'Unity.

Di solito, ha senso che la finestra principale si iscriva a questo evento: può aprire la finestra di dialogo e impostare il suo contesto di dati sul ViewModel che viene passato con un evento generato.

L'impostazione in questo modo consente a ViewModels di chiedere all'applicazione di aprire una finestra di dialogo e di rispondere alle azioni dell'utente lì senza conoscere nulla sull'interfaccia utente, quindi per la maggior parte MVVM-ness rimane completa.

Ci sono momenti, tuttavia, in cui l'interfaccia utente deve aumentare i dialoghi, il che può rendere le cose un po 'più complicate. Considera, ad esempio, se la posizione della finestra di dialogo dipende dalla posizione del pulsante che la apre. In questo caso è necessario disporre di alcune informazioni specifiche dell'interfaccia utente quando si richiede l'apertura di una finestra di dialogo. Generalmente creo una classe separata che contiene un ViewModel e alcune informazioni relative all'interfaccia utente. Purtroppo alcuni accoppiamenti sembrano inevitabili lì.

Pseudo codice di un gestore di pulsanti che solleva una finestra di dialogo che necessita di dati sulla posizione dell'elemento:

ButtonClickHandler(sender, args){
    var vm = DataContext as ISomeDialogProvider; // check for null
    var ui_vm = new ViewModelContainer();
    // assign margin, width, or anything else that your custom dialog might require
    ...
    ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
    // raise the dialog show event
}

La vista della finestra di dialogo si legherà ai dati di posizionamento e passerà il ViewModel contenuto all'interno ContentControl. Il ViewModel stesso non sa ancora nulla sull'interfaccia utente.

In generale, non utilizzo la DialogResultproprietà return del ShowDialog()metodo e non mi aspetto che il thread si blocchi fino alla chiusura della finestra di dialogo. Una finestra di dialogo modale non standard non sempre funziona così, e in un ambiente composito spesso non si desidera che un gestore di eventi si blocchi in questo modo. Preferisco lasciare che ViewModels si occupi di questo: il creatore di ViewModel può iscriversi ai suoi eventi rilevanti, impostare metodi di commit / annullamento, ecc., Quindi non è necessario fare affidamento su questo meccanismo dell'interfaccia utente.

Quindi invece di questo flusso:

// in code behind
var result = somedialog.ShowDialog();
if (result == ...

Io uso:

// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit 
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container

Lo preferisco in questo modo perché la maggior parte dei miei dialoghi sono controlli pseudo-modali non bloccanti e farlo in questo modo sembra più semplice che aggirarlo. Anche facile da testare l'unità.


Grazie per la risposta dettagliata! Recentemente ho scoperto che il mio problema più grande è quando devo avere un MainViewModel che comunica con altri modelli di vista per gestire il flusso dell'applicazione. Tuttavia sembra che MVVM + Mediator sembra essere l'approccio popolare.
gennaio

2
Il Mediatore aiuta sicuramente, il modello di aggregatore di eventi (Prism ha una buona implementazione) è anche molto utile quando l'obiettivo è quello di un accoppiamento basso. Inoltre, il modello di visualizzazione principale in genere ha modelli di visualizzazione figlio propri e non dovrebbe avere problemi di comunicazione con essi. Devi utilizzare un mediatore o / e un aggregatore di eventi quando i tuoi modelli di visualizzazione figlio devono interagire con altri moduli nella tua app di cui non sono necessariamente a conoscenza, inclusa l'interfaccia utente (il mio esempio di dialogo riguarda questo caso particolare).
Egor,

1
Le linee guida per lavorare con finestre di dialogo e finestre sono state davvero utili. Tuttavia, sono bloccato con alcuni problemi: 1. Come si imposta il titolo della finestra dalla vista? 2. Come gestisci l'impostazione della finestra del proprietario?
djskinner,

@Daniel Skinner: presumo che tu stia parlando di dialoghi qui, correggimi se sbaglio. Il titolo della finestra di dialogo è solo un'altra proprietà e puoi associarlo a quello che ti piace. Se hai seguito il mio approccio con una classe viewmodel della finestra di dialogo di base (facciamo finta che abbia una proprietà title), quindi nella tua finestra di dialogo tutta generica puoi usare l'associazione da UI a UI per impostare il titolo su {Binding Path = DataContext.Title, ElementName = NameOfContentPresenter}. La finestra del proprietario è un po 'più complicata: significa che il mediatore che apre effettivamente la finestra di dialogo deve conoscere la vista dell'app di root.
Egor,

In effetti, lo riprendo, indipendentemente da come lo strutturi ad un certo punto, chiunque stia effettivamente aprendo la finestra di dialogo deve avere un riferimento alla finestra / vista dell'app di root. Notate dove ho detto "Di solito, ha senso che la finestra principale si iscriva a questo evento: può aprire la finestra di dialogo e impostare il suo contesto di dati sul modello che viene passato con un evento generato." Qui è dove impostare il proprietario.
Egor,

6

Jason Dolinger ha realizzato un ottimo screencast di MVVM. Come menzionato da Egor, non esiste un buon esempio. Sono dappertutto. La maggior parte sono buoni esempi di MVVM, ma non quando si affrontano problemi complessi. Ognuno ha la propria strada. Laurent Bugnion ha anche un buon modo di comunicare tra i modelli. http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx Cinch è anche un buon esempio. Paul Stovel ha un buon post che spiega molto anche con il suo framework Magellan.


3

Hai visto Caliburn ? L'esempio di ContactManager contiene molte cose buone. Gli esempi WPF generici forniscono anche una buona panoramica dei comandi. La documentazione è abbastanza buona e i forum sono attivi. Consigliato!





2

Ho anche condiviso la tua frustrazione. Sto scrivendo una domanda e avevo questi 3 requisiti:

  • Estensibile
  • WPF con MVVM
  • Esempi compatibili con GPL

Tutto quello che ho trovato erano frammenti, quindi ho appena iniziato a scriverlo il meglio che potevo. Dopo averci approfondito un po ', mi sono reso conto che potrebbero esserci altre persone (come te) che potrebbero usare un'applicazione di riferimento, quindi ho riformulato le cose generiche in un framework di applicazioni WPF / MVVM e le ho rilasciate sotto LGPL. L'ho chiamato SoapBox Core . Se vai alla pagina dei download, vedrai che viene fornito con una piccola applicazione demo e il codice sorgente per tale applicazione demo è disponibile anche per il download. Spero che lo trovi utile. Inoltre, inviami un'e-mail a scott {at} soapboxautomation.com se desideri ulteriori informazioni.

EDIT : ha anche pubblicato un articolo di CodeProject che spiega come funziona.


2

Ho scritto un semplice esempio MVVM da zero sul progetto di codice qui è il collegamento MVVM WPF passo dopo passo . Si parte da una semplice architettura a 3 livelli e ti laurea per utilizzare un framework come PRISM.

inserisci qui la descrizione dell'immagine


1

Anche io ho condiviso la frustrazione fino a quando non ho preso in mano la questione. Ho avviato IncEditor.

IncEditor ( http://inceditor.codeplex.com ) è un editor che tenta di presentare agli sviluppatori WPF, MVVM e MEF. L'ho avviato e sono riuscito a ottenere alcune funzionalità come il supporto 'tema'. Non sono un esperto di WPF, MVVM o MEF, quindi non posso aggiungere molte funzionalità. Vi faccio una sincera richiesta a voi ragazzi di renderlo migliore in modo che capricci come me possano capirlo meglio.

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.