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 INotifyPropertyChanged
per supportare l'associazione e puoi associare le proprietà direttamente ai controlli dell'interfaccia utente, ma potresti anche voler implementare IDataErrorInfo
per 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 IDataErrorInfo
e 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à Item
che 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 DialogResult
proprietà 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à.