Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Modelli di struttura

Problemi e soluzioni con il modello MVVM (Model-View-View Model)

Robert McCarter

Scaricare il codice di esempio

Windows Presentation Foundation (WPF) e Silverlight fornisce API complete per la creazione di applicazioni moderne, ma comprensione e applicando tutte le funzionalità WPF in harmony tra loro per creare ben progettata e gestite con facilità applicazioni possono risultare difficile. Dove avvia? E qual è il modo corretto per comporre l'applicazione?

Il modello di progettazione Model View ViewModel (MVVM) descrive un approccio comune per la creazione di applicazioni WPF e Silverlight. È un potente strumento per la creazione di applicazioni e un linguaggio comune per illustrare la struttura dell'applicazione con gli sviluppatori. MVVM è un modello molto utile, ma è ancora relativamente giovani e confusione.

Quando è disponibile il modello di progettazione MVVM e quando è necessario? Come l'applicazione deve essere strutturato? Quanto lavoro è il livello ViewModel scrivere e mantenere e quali alternative esistono per ridurre la quantità di codice del livello ViewModel? Proprietà correlate all'interno del modello di gestione delle elegantemente? Come deve esporre gli insiemi all'interno del modello per la visualizzazione Dove ViewModel oggetti da creare istanze e collegato al modello di oggetti?

In questo articolo verrà spiegato come funziona il ViewModel e vengono illustrati alcuni vantaggi e problematiche relative all'implementazione di un ViewModel nel codice. Inoltre verrà illustrano le fasi alcuni esempi concreti di utilizzare ViewModel come un documento di gestione per l'esposizione di oggetti modello di visualizzazione.

Modello, ViewModel e visualizzazione

Ogni applicazione WPF e Silverlight che ho lavorato finora aveva la stessa struttura di alto livello del componente. Il modello è stato la base dell'applicazione e molto impegno è andato in fase di progettazione in base all'analisi orientata e procedure consigliate per la progettazione (OOAD).

Per me il modello rappresenta il fulcro dell'applicazione, che rappresentano beni aziendali più importanti e più importante perché acquisisce tutte le entità aziendali complesse, le loro relazioni e le loro funzionalità.

Mouse è sopra il modello è il ViewModel. I due obiettivi principali della ViewModel sono per rendere il modello facilmente utilizzabili da visualizzazione XAML WPF/e per separare e incapsulare il modello dalla visualizzazione. Questi sono obiettivi eccellente, sebbene per motivi pragmatico siano talvolta interrotti.

Si genera ViewModel conoscenza delle modalità di interazione dell'utente con l'applicazione ad alto livello. Tuttavia, è una parte importante del modello di progettazione MVVM la ViewModel SA nulla sulla visualizzazione. Questo consente ai progettisti di interazione e artisti di grafica creare interfacce utente di bellissimi e funzionali in cima al ViewModel durante la stretta collaborazione con gli sviluppatori per progettare un ViewModel adatto supportare gli sforzi. Inoltre, separazione tra viste e ViewModel consente inoltre ViewModel da più unità riutilizzabili e verificabile.

Per applicare una separazione rigorosa tra i livelli del modello, visualizzazione e ViewModel, vorrei creare ogni livello come un progetto di Visual Studio separato. In combinazione con l'utilità riutilizzabili, assembly eseguibile principale e i progetti di test unit (essere moltissime, destra?), questo può causare numerosi progetti e assembly, come illustrato in di Figura 1.

Figure 1 The Components of an MVVM Application

Figura 1 I componenti di un'applicazione MVVM

Dato il numero elevato di progetti, questo approccio rigorosa separazione è ovviamente più utile su progetti di grandi dimensioni. Per piccole applicazioni con una o due sviluppatori, i vantaggi di questa separazione rigorosa potrebbero non superare l'inconveniente di creazione, configurazione e gestione di più progetti, separando così semplicemente il codice in diversi spazi dei nomi all'interno dello stesso progetto può fornire più di isolamento sufficiente.

La scrittura e la gestione di un ViewModel non è semplice e non deve essere intrapresa leggera. Tuttavia, la risposta alle domande di base, quando è opportuno il modello di progettazione MVVM e quando è necessario, spesso si trova nel modello di dominio.

Nei progetti di grandi dimensioni, il modello di dominio può essere molto complesso con centinaia di classi progettate attentamente elegantemente collaborare per qualsiasi tipo di applicazione, inclusi i servizi Web, applicazioni WPF o ASP.NET. Il modello può includere diversi assembly interagiscono tra loro e nelle organizzazioni di grandi dimensioni il modello di dominio talvolta è creato e gestito da un team di sviluppo specializzati.

Quando si dispone di un modello di dominio di grandi dimensioni e complesse, quasi sempre è opportuno introdurre un livello ViewModel.

D'altra parte, a volte il modello di dominio è semplice, forse non più di un livello ridotto sul database. Le classi possono essere generate automaticamente e sono spesso implementare INotifyPropertyChanged. L'interfaccia utente è in genere un insieme di elenchi o griglie con form di modifica consente di manipolare i dati sottostanti. Il set di strumenti Microsoft è sempre stato molto valido per creare questi tipi di applicazioni facilmente e rapidamente.

Se il modello o l'applicazione rientra in questa categoria, un ViewModel sarebbe probabilmente imporre eccessivamente elevato overhead senza sufficientemente benefitting la progettazione dell'applicazione.

Detto questo, anche in questi casi che il ViewModel può ancora fornire valore. Ad esempio, il ViewModel è un ottimo luogo per implementare la funzionalità di annullamento. In alternativa, è possibile utilizzare MVVM per una parte dell'applicazione (ad esempio la gestione dei documenti, come verrà illustrato più avanti) e pragmatically esporre il modello direttamente alla visualizzazione.

Perché utilizzare un ViewModel?

Se un ViewModel sembra appropriato per l'applicazione, esistono ancora domande a risposta prima di iniziare la codifica. Una delle prime è come ridurre il numero delle proprietà proxy.

La separazione della visualizzazione dal modello promosso tramite il modello di progettazione MVVM è un aspetto importante e utile del motivo. Di conseguenza, se una classe modello presenta 10 proprietà che devono essere esposto nella visualizzazione, il ViewModel in genere finisce con 10 proprietà identiche che semplicemente proxy la chiamata all'istanza del modello sottostante. Queste proprietà proxy solitamente generano un evento di modifica proprietà quando viene impostato per indicare la visualizzazione che è stata modificata la proprietà.

Non tutte le proprietà modello devono avere una proprietà proxy ViewModel, ma ogni proprietà modello che deve essere esposto nella visualizzazione presentano una proprietà proxy. La proprietà proxy è in genere simile al seguente:

public string Description {
  get { 
    return this.UnderlyingModelInstance.Description; 
  }
  set {
    this.UnderlyingModelInstance.Description = value;
    this.RaisePropertyChangedEvent("Description");
  }
}

Qualsiasi applicazione non semplice avrà decine o centinaia di classi modello che devono essere esposti all'utente mediante ViewModel in questo modo. Si tratta semplicemente intrinseco per la separazione fornita da MVVM.

Scrivere tali proprietà proxy è interessante e pertanto tendente all'errore, soprattutto perché la generazione dell'evento di modifica delle proprietà richiede una stringa che deve corrispondere al nome della proprietà (e non verrà incluso in qualsiasi refactoring del codice automatico). Per eliminare questi eventi proxy, la soluzione comune consiste nell'esporre direttamente l'istanza di modello dal wrapper ViewModel, quindi è il modello di dominio implementa l'interfaccia di INotifyPropertyChanged:

public class SomeViewModel {
  public SomeViewModel( DomainObject domainObject ) {
    Contract.Requires(domainObject!=null, 
      "The domain object to wrap must not be null");
    this.WrappedDomainObject = domainObject;
  }
  public DomainObject WrappedDomainObject { 
    get; private set; 
  }
...

Pertanto, il ViewModel ancora può esporre i comandi e le proprietà aggiuntive necessarie per la visualizzazione senza duplicare le proprietà del modello o creare numerose proprietà proxy. Questo approccio ha certamente l'attrattiva, soprattutto se le classi di modelli già implementano l'interfaccia INotifyPropertyChanged. Con il modello di implementare questa interfaccia non è necessariamente un aspetto negativo ed era comune anche con Microsoft .NET Framework 2.0 e le applicazioni Windows Form. Appesantiscano il modello di dominio, pertanto sarebbe utile per le applicazioni ASP.NET o i servizi di dominio.

Con questo approccio la visualizzazione presenta una dipendenza del modello, ma è solo una dipendenza indiretta tramite l'associazione dati, che non richiede un riferimento di progetto dal progetto di visualizzazione per il progetto modello. Pertanto per ragioni puramente pragmatico questo approccio è talvolta utile.

Tuttavia, questo approccio violano lo spirito del modello di progettazione MVVM e riduce la possibilità di introdurre nuove funzionalità specifiche ViewModel successivamente (ad esempio funzionalità di annullamento). È stato rilevato scenari con questo approccio causa un notevole bit di variazioni. Immaginate la situazione non insolita in cui è presente un'associazione dati a una proprietà di nidificazione. Se la persona ha un indirizzo ViewModel Person è il contesto di dati corrente, l'associazione dati potrebbe essere questo aspetto:

{Binding WrappedDomainObject.Address.Country}

Se fosse necessario introdurre funzionalità aggiuntive ViewModel sull'oggetto indirizzo, sarà necessario rimuovere i riferimenti di associazione dati per WrappedDomainObject.Address ed utilizzare nuove proprietà ViewModel. Tale situazione è problematica perché gli aggiornamenti per l'associazione dei dati XAML (e possibilmente contesti di dati) sono difficili da verificare. La visualizzazione è un componente che non dispone di test di regressione automatizzato e complete.

Proprietà dinamiche

Le soluzioni per la proliferazione delle proprietà proxy consiste nell'utilizzare il nuovo supporto .NET Framework 4 e WPF per oggetti dinamici e dispatch del metodo dinamico. Quest'ultimo consente di determinare come gestire la lettura o scrittura a una proprietà che non esiste sulla classe in fase di esecuzione. Ciò significa che è possibile eliminare tutte le proprietà proxy a mano il ViewModel durante l'incapsulamento comunque il modello sottostante. Si noti che 4 Silverlight non supporta l'associazione per le proprietà dinamiche.

Il modo più semplice per implementare questa funzionalità è che la classe base ViewModel estende la nuova classe System.Dynamic.DynamicObject e l'override dei metodi TryGetMember e TrySetMember. DLR (Dynamic Language Runtime) chiama questi due metodi quando non esiste la proprietà di riferimento sulla classe, consentendo alla classe determinare come implementare le proprietà mancanti in fase di esecuzione. In combinazione con una piccola quantità di reflection, la classe ViewModel può dinamicamente il proxy di accesso alle proprietà per l'istanza di modello sottostante in poche righe di codice:

public override bool TryGetMember(
  GetMemberBinder binder, out object result) {

  string propertyName = binder.Name;
  PropertyInfo property = 
    this.WrappedDomainObject.GetType().GetProperty(propertyName);

  if( property==null || property.CanRead==false ) {
    result = null;
    return false;
  }

  result = property.GetValue(this.WrappedDomainObject, null);
  return true;
}

Il metodo viene avviato tramite reflection per trovare la proprietà sull'istanza del modello sottostante. (Per ulteriori dettagli, vedere giugno 2007 “ CLR Inside Out ” colonna “ di riflessi sulla reflection ”). Se il modello non include una proprietà, il metodo avrà esito negativo con la restituzione di false e l'errore di associazione dati. Se la proprietà esiste, il metodo utilizza le informazioni sulle proprietà da recuperare e restituire il valore della proprietà del modello. Si tratta di lavoro maggiore rispetto a metodo di ottenere la proprietà proxy tradizionali, ma questa è l'unica implementazione che è necessario scrivere per tutti i modelli e tutte le proprietà.

Proprietà set è la vera potenza dell'approccio proprietà proxy dinamiche. In TrySetMember, è possibile includere la logica comune, ad esempio la generazione di eventi di proprietà modificata. Il codice simile al seguente:

public override bool TrySetMember(
  SetMemberBinder binder, object value) {

  string propertyName = binder.Name;
  PropertyInfo property = 
    this.WrappedDomainObject.GetType().GetProperty(propertyName);

  if( property==null || property.CanWrite==false )
    return false;

  property.SetValue(this.WrappedDomainObject, value, null);

  this.RaisePropertyChanged(propertyName);
  return true;
}

Il metodo avvia nuovamente, tramite reflection per ottenere le proprietà dell'istanza di modello sottostante. Se la proprietà non esiste o se la proprietà è di sola lettura, il metodo avrà esito negativo con la restituzione di false. Se la proprietà esiste nell'oggetto dominio, le informazioni sulle proprietà consente di impostare la proprietà del modello. È possibile includere qualsiasi logica comune a tutti i metodi di impostazione di proprietà. In questo esempio di codice semplicemente generare l'evento Modifica delle proprietà per la proprietà che è sufficiente impostare, ma è possibile eseguire facilmente più.

Una delle sfide di incapsulamento di un modello è che il modello è spesso cosa Unified Modeling Language chiama proprietà derivata. Ad esempio, una classe Person è probabilmente una proprietà Data di nascita e una proprietà Age derivata. La proprietà Age è di sola lettura e calcola automaticamente la scadenza in base alla data di nascita e la data corrente:

public class Person : DomainObject {
  public DateTime BirthDate { 
    get; set; 
  }

  public int Age {
    get {
      var today = DateTime.Now;
      // Simplified demo code!
      int age = today.Year - this.BirthDate.Year;
      return age;
    }
  }
...

Quando le modifiche alla proprietà Data di nascita, la proprietà Age anche implicitamente modificata perché la scadenza è derivata matematicamente dalla data di nascita. Pertanto quando viene impostata la data di nascita, la classe ViewModel deve generare un evento di modifica delle proprietà per la proprietà Data di nascita e la proprietà Age. Con l'approccio ViewModel dinamico, farlo automaticamente rendendo questa relazione inter-property esplicito all'interno del modello.

Innanzitutto, è necessario un attributo personalizzato per acquisire la proprietà relazione:

[AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
public sealed class AffectsOtherPropertyAttribute : Attribute {
  public AffectsOtherPropertyAttribute(
    string otherPropertyName) {
    this.AffectsProperty = otherPropertyName;
  }

  public string AffectsProperty { 
    get; 
    private set; 
  }
}

È possibile impostare AllowMultiple su true per il supporto di scenari in cui una proprietà può influire sulle altre più proprietà. Applicare questo attributo a codify la relazione tra la data di nascita ed età direttamente nel modello è semplice:

[AffectsOtherProperty("Age")]
public DateTime BirthDate { get; set; }

Per utilizzare il nuovo modello metadati all'interno della classe ViewModel dinamico, è possibile aggiornare il metodo TrySetMember con tre ulteriori righe di codice, ora in modo che appaia simile al seguente:

public override bool TrySetMember(
  SetMemberBinder binder, object value) {
...
  var affectsProps = property.GetCustomAttributes(
    typeof(AffectsOtherPropertyAttribute), true);
  foreach(AffectsOtherPropertyAttribute otherPropertyAttr 
    in affectsProps)
    this.RaisePropertyChanged(
      otherPropertyAttr.AffectsProperty);
}

Con informazioni di proprietà riflesso già a disposizione, il metodo GetCustomAttributes può restituire qualsiasi attributo AffectsOtherProperty sulla proprietà modello. Quindi il codice scorre semplicemente su attributi, la generazione di eventi di proprietà modificata per ciascuno di essi. Pertanto, le modifiche alla proprietà BirthDate attraverso il ViewModel ora generano automaticamente data di nascita ed età eventi di proprietà modificata.

È importante tenere presente che se si programma in modo esplicito una proprietà della classe ViewModel dinamico o, più probabilmente, sul modello specifico ViewModel classi derivate, DLR non chiamerà i metodi TryGetMember e TrySetMember e verrà invece chiamare direttamente le proprietà. In tal caso, si perde il funzionamento automatico. Tuttavia il codice potrebbe essere rielaborato facilmente in modo che le proprietà personalizzate possono utilizzare anche questa funzionalità.

Restituire Let’s al problema dell'associazione dati su una proprietà profondamente nidificata (dove il ViewModel è il contesto di dati corrente WPF) è simile al seguente:

{Binding WrappedDomainObject.Address.Country}

Utilizzando la proprietà proxy dinamiche significa che l'oggetto dominio con wrapper sottostante non viene esposta, in modo che l'associazione dati effettivamente risulterebbe simile al seguente:

{Binding Address.Country}

In questo caso, la proprietà Address sarebbe comunque accedere all'istanza di indirizzo modello sottostante direttamente. Tuttavia, ora quando si desidera presentare un ViewModel racchiudere l'indirizzo, è sufficiente aggiungere una nuova proprietà sulla classe Person ViewModel. La nuova proprietà Address è molto semplice:

public DynamicViewModel Address {
  get {
    if( addressViewModel==null )
      addressViewModel = 
        new DynamicViewModel(this.Person.Address);
    return addressViewModel;
  }
}

private DynamicViewModel addressViewModel;

Nessuna associazione dei dati XAML devono essere modificate perché la proprietà è ancora chiamata indirizzo, ma ora DLR chiama la nuova proprietà concreta anziché il metodo TryGetMember dinamico. (Si noti che la creazione dell'istanza lazy all'interno di questa proprietà Address non è thread-safe. Tuttavia, solo la visualizzazione deve accedere il ViewModel e visualizzazione WPF/Silverlight è a thread singolo, pertanto non costituisce un problema.)

Questo approccio può essere utilizzato anche quando il modello implementa INotifyPropertyChanged. Il ViewModel possibile notare questo e scegliere non di eventi di proprietà modificata proxy. In questo caso, che li attende dall'istanza di modello sottostante e quindi re-raises gli eventi come proprio. Nel costruttore della classe ViewModel dinamico, è possibile eseguire il controllo e ricordare il risultato:

public DynamicViewModel(DomainObject model) {
  Contract.Requires(model != null, 
    "Cannot encapsulate a null model");
  this.ModelInstance = model;

  // Raises its own property changed events
  if( model is INotifyPropertyChanged ) {
    this.ModelRaisesPropertyChangedEvents = true;
    var raisesPropChangedEvents = 
      model as INotifyPropertyChanged;
    raisesPropChangedEvents.PropertyChanged +=
      (sender,args) => 
      this.RaisePropertyChanged(args.PropertyName);
  }
}

Per impedire eventi di proprietà modificata duplicati, è inoltre necessario apportare una lieve modifica al metodo TrySetMember.

if( this.ModelRaisesPropertyChangedEvents==false )
  this.RaisePropertyChanged(property.Name);

Pertanto è possibile utilizzare una proprietà proxy dinamiche per semplificare notevolmente il livello di ViewModel, eliminando le proprietà standard per il proxy. Questo riduce notevolmente la scrittura del codice, test, documentazione e manutenzione a lungo termine. L'aggiunta di nuove proprietà per il modello non richiede l'aggiornamento al livello ViewModel solo in casi molto particolare visualizzazione logica della nuova proprietà. Inoltre, questo approccio consente di risolvere problemi difficili come proprietà correlate. Il metodo TrySetMember comune potrebbe inoltre consentono di implementare una funzionalità di annullamento perché tutto il flusso di modifica proprietà eseguita dall'utente tramite il metodo TrySetMember.

Vantaggi e svantaggi

Molti sviluppatori sono leery di reflection e DLR causa di problemi di prestazioni. Nel mio lavoro sono stati trovati questo è un problema. La riduzione delle prestazioni per l'utente durante l'impostazione di una singola proprietà dell'interfaccia utente non è probabilmente essere notato. Che non può essere il caso di interfacce utente altamente interattivi, quali superfici di progettazione multi-touch.

Il problema di prestazioni principali solo riguarda il popolamento iniziale della visualizzazione quando sono presenti numerosi campi. Problemi di utilizzabilità naturalmente necessario limitare il numero di campi che si espone su qualsiasi schermo in modo che le prestazioni delle associazioni dati iniziali tramite questo approccio DLR sono rilevabile.

Tuttavia, prestazioni devono sempre essere attentamente monitorata e comprensibile in relazione all'esperienza utente. Il semplice approccio descritto in precedenza può essere riscritto con la cache di reflection. Per ulteriori informazioni, vedere Joel Pobar dell'articolo nel numero di luglio 2005 di di MSDN Magazine.

Contiene alcuni validità per l'argomento la leggibilità del codice e gestibilità sono influenzati negativamente utilizzando questo approccio perché il livello di visualizzazione sembra essere riferimento a proprietà di ViewModel che non esiste. Tuttavia, credo che i vantaggi di eliminare la maggior parte delle proprietà codificato manualmente proxy molto superano i problemi, specialmente con documentazione appropriata sul ViewModel.

L'approccio di proprietà proxy dinamica ridurre o eliminare la possibilità di offuscare il livello del modello, poiché ora fanno riferimento le proprietà nel modello di nome in XAML. Utilizzando la proprietà proxy tradizionali non limita la capacità di offuscare il modello perché fanno riferimento direttamente le proprietà e dovrebbe essere offuscate con il resto dell'applicazione. Tuttavia, come la maggior parte degli strumenti di offuscamento non funzionano ancora con XAML/BAML, questo è in gran parte irrilevante. Un cracker di codice può iniziare da XAML/BAML e al livello del modello in entrambi i casi.

Infine, questo approccio potrebbe essere sfruttato attribuisce 
properties modello con i metadati relativi alla protezione e previsto ViewModel responsabile dell'imposizione di protezione. Protezione non sembra un compito specifico della visualizzazione e ritengo che questo è l'inserimento troppe responsabilità nel ViewModel. In questo caso, sarebbe più appropriato un approccio orientato ai aspetto applicato all'interno del modello.

Insiemi

Gli insiemi sono uno degli aspetti più difficili e meno soddisfacenti del modello di progettazione MVVM. Se il modello di modifica un insieme del modello sottostante, è responsabilità del ViewModel esporre in qualche modo la modifica in modo che la visualizzazione può aggiornarsi in modo appropriato.

Purtroppo, in tutte le probabilità che il modello non espone gli insiemi che implementano l'interfaccia INotifyCollectionChanged. In .NET Framework 3.5, questa interfaccia è System.Windows.dll, quale fortemente sconsiglia l'utilizzo del modello. Fortunatamente, in .NET Framework 4, questa interfaccia è migrati System.dll, rendendo molto più naturale per utilizzare insiemi osservabili da all'interno del modello.

Osservabili insiemi nel modello di aprire nuove possibilità per lo sviluppo di modelli e possono essere utilizzati nelle applicazioni Silverlight e Windows Form. Questo è attualmente l'approccio consigliato perché è molto più semplice rispetto a qualsiasi altro e sono felice che 
Interface INotifyCollectionChanged viene spostato in un assembly più comuni.

Senza osservabili insiemi nel modello, è meglio che può essere eseguito per esporre un altro meccanismo, probabilmente gli eventi personalizzati, sul modello per indicare quando l'insieme viene modificato. Questa operazione deve essere eseguita in modo specifico modello. Ad esempio, se la classe Person è un insieme di indirizzi potrebbe esporre eventi quali:

public event EventHandler<AddressesChangedEventArgs> 
  NewAddressAdded;
public event EventHandler<AddressesChangedEventArgs> 
  AddressRemoved;

Questo è preferibile generare un evento personalizzato insieme progettato specificamente per ViewModel WPF. Tuttavia, è comunque difficile per esporre le modifiche insieme al ViewModel. Probabilmente, l'unica soluzione è per generare un evento di modifica delle proprietà nella proprietà dell'insieme ViewModel intero. Questa è una soluzione non soddisfacenti nel migliore dei casi.

Un altro problema con gli insiemi consiste nel determinare quando o se eseguire il wrapping di ogni istanza di modello dell'insieme in un'istanza ViewModel. Per gli insiemi di dimensioni inferiori, il ViewModel può esporre un nuovo insieme osservabile e copia tutto nell'insieme modello sottostante nell'insieme osservabile ViewModel, disposizione di ciascun elemento di modello dell'insieme in un'istanza ViewModel corrispondente al passaggio. Il ViewModel potrebbe essere necessario attendere gli eventi insieme modificato trasmettere le modifiche dell'utente al modello sottostante.

Tuttavia, per grandi insiemi che verranno esposte in qualche forma di Pannello virtualizing, l'approccio più semplice e più pragmatico è solo per esporre direttamente gli oggetti del modello.

Creazione di istanze di ViewModel

Un altro problema con il modello di progettazione MVVM esaminato raramente è quando e dove dovrebbero essere istanziate istanze ViewModel. Questo problema è spesso trascurato nelle discussioni dei modelli di progettazione simile come MVC.

Preferenza consiste nello scrivere un singleton ViewModel fornisce gli oggetti ViewModel principali da cui la visualizzazione è facile recuperare tutti gli altri oggetti ViewModel come richiesto. Spesso questo oggetto ViewModel master fornisce le implementazioni di comando in modo che la visualizzazione è in grado di supportare l'apertura dei documenti.

Tuttavia, la maggior parte delle applicazioni che ho lavorato con forniscono un'interfaccia incentrata sui documenti, in genere utilizzando un'area di lavoro con schede simile a Visual Studio. Pertanto nel livello ViewModel è necessario pensare in termini di documenti e documenti di espongono uno o più oggetti ViewModel disposizione particolari modello di oggetti. Comandi WPF standard nel livello ViewModel possono quindi utilizzare il livello di persistenza per recuperare gli oggetti necessari, eseguirne il wrapping in istanze ViewModel e creare ViewModel responsabili del documento per visualizzarli.

Nell'applicazione di esempio incluso in questo articolo è il comando ViewModel per la creazione di un nuovo utente:

internal class OpenNewPersonCommand : ICommand {
...
  // Open a new person in a new window.
  public void Execute(object parameter) {
    var person = new MvvmDemo.Model.Person();
    var document = new PersonDocument(person);
    DocumentManager.Instance.ActiveDocument = document;
  }
}

La gestione di documenti ViewModel fa riferimento nell'ultima riga è un singleton che gestisce tutti i ViewModel documenti aperti. La domanda è: come l'insieme dei documenti ViewModel ottenere esposta nella visualizzazione?

Il controllo WPF incorporato non fornisce il tipo di utenti derivino da prevedere potente interfaccia a documenti multipli. Fortunatamente, l'ancoraggio di terze parti e dell'area di lavoro a schede prodotti è disponibili. La maggior parte di essi impegnarsi emulare l'aspetto del documento a schede di Visual Studio, comprese le finestre degli strumenti ancorate, dividere visualizzazioni, finestre popup Ctrl + TAB (con visualizzazioni mini-document) e altro ancora.

Sfortunatamente, la maggior parte di questi componenti non forniscono supporto incorporato per il modello di progettazione MVVM. Ma va bene, poiché è possibile applicare facilmente il modello di progettazione adattatore per collegare il gestore del documento ViewModel al componente di visualizzazione di terze parti.

Scheda di gestione documenti

La progettazione scheda illustrata in Figura 2 garantisce che il ViewModel non richiede alcun riferimento alla visualizzazione in modo che rispetta gli obiettivi principali del modello di progettazione MVVM. (Tuttavia, in questo caso, il concetto di un documento è definito nel livello ViewModel anziché il livello del modello perché è semplicemente un concetto di interfaccia utente.)

Figure 2 Document Manager View Adapter

Figura 2 Document Manager Visualizza scheda

Il gestore del documento ViewModel è responsabile di mantenere l'insieme dei documenti aperti ViewModel e sapere quale documento è attualmente attivo. Questo consente al livello ViewModel per aprire e chiudere i documenti utilizzando il gestore del documento e modificare il documento attivo senza la conoscenza della visualizzazione. Il lato ViewModel di questo approccio è piuttosto semplice. Le classi ViewModel nell'applicazione di esempio sono riportate in di Figura 3.

Figure 3 The ViewModel Layer’s Document Manager and Document Classes

Figura 3 il layer di ViewModel Document Manager e classi di documenti

La classe base del documento espone più interna del ciclo di vita metodi (attivato, LostActivation e DocumentClosed) che vengono chiamati da Gestione documenti per mantenere il documento aggiornato su cosa sta succedendo. Il documento implementa inoltre un'interfaccia INotifyPropertyChanged che possono supportare l'associazione dati. Adattatore dati, ad esempio, Associa proprietà Title di visualizzazione del documento alla proprietà DocumentTitle del ViewModel.

La parte più complessa di questo approccio è la classe di adattatori e ho fornito una copia di lavoro del progetto che accompagna questo articolo. La scheda sottoscrive eventi nella gestione di documenti e utilizza tali eventi per mantenere aggiornato il controllo dell'area di lavoro a schede. Ad esempio, quando il gestore del documento indica che è stato aperto un nuovo documento, l'adattatore riceve un evento, esegue il wrapping documento ViewModel in qualunque controllo WPF è necessaria e quindi espone tale controllo nell'area di lavoro a schede.

La scheda dispone di un'altra responsabilità: mantenere la gestione di documenti ViewModel sincronizzato con le azioni dell'utente. L'adattatore deve pertanto anche attendere gli eventi dal controllo a schede area di lavoro in modo che quando l'utente modifica il documento attivo o un documento chiuso l'adattatore può notificare al gestore di documento.

Mentre questa logica è molto complesso, sono presenti alcuni avvertimenti. Esistono diversi scenari in cui il codice diventa rientrante e deve essere gestito correttamente. Ad esempio, se il ViewModel Gestione documenti viene utilizzato per chiudere un documento, l'adattatore verrà visualizzato l'evento dal gestore del documento e chiudere la finestra di documento fisico nella visualizzazione. In questo modo il controllo a schede area di lavoro generare un evento di chiusura del documento, l'adattatore riceve inoltre, e il gestore eventi dell'adattatore, naturalmente, invierà la gestione di documenti che è necessario chiudere il documento. Pertanto deve essere sympathetic per consentire la gestione di documenti, è già stato chiuso il documento.

Altre difficoltà è che scheda della visualizzazione deve essere in grado di collegare un controllo documento a schede di visualizzazione con un oggetto ViewModel Document. La soluzione più affidabile è utilizzare un WPF collegato proprietà di dipendenza. L'adattatore dichiara una proprietà di dipendenza associata privata utilizzato per collegare il controllo della finestra di visualizzazione documento istanza ViewModel.

Nel progetto di esempio per questo articolo, si utilizza un componente a schede area di lavoro open source denominato AvalonDock, in modo che le proprietà di dipendenza associata è simile al codice illustrato in di Figura 4.

Figura 4 collegamento del controllo visualizzazione e documento ViewModel

private static readonly DependencyProperty 
  ViewModelDocumentProperty =
  DependencyProperty.RegisterAttached(
  "ViewModelDocument", typeof(Document),
  typeof(DocumentManagerAdapter), null);

private static Document GetViewModelDocument(
  AvalonDock.ManagedContent viewDoc) {

  return viewDoc.GetValue(ViewModelDocumentProperty) 
    as Document;
}

private static void SetViewModelDocument(
  AvalonDock.ManagedContent viewDoc, Document document) {

  viewDoc.SetValue(ViewModelDocumentProperty, document);
}

Quando la scheda Crea un nuovo controllo finestra Visualizza, imposta la proprietà associata sul controllo finestra nuova per il documento sottostante ViewModel (vedere di Figura 5). È anche possibile vedere l'associazione dati titolo qui configurata e vedere modalità di configurazione della scheda il contesto dei dati e il contenuto del controllo Visualizza documento.

Figura 5 impostazione della proprietà associata

private AvalonDock.DocumentContent CreateNewViewDocument(
  Document viewModelDocument) {

  var viewDoc = new AvalonDock.DocumentContent();
  viewDoc.DataContext = viewModelDocument;
  viewDoc.Content = viewModelDocument;

  Binding titleBinding = new Binding("DocumentTitle") { 
    Source = viewModelDocument };

  viewDoc.SetBinding(AvalonDock.ManagedContent.TitleProperty, 
    titleBinding);
  viewDoc.Closing += OnUserClosingDocument;
  DocumentManagerAdapter.SetViewModelDocument(viewDoc, 
    viewModelDocument);

  return viewDoc;
}

Impostando contenuto del controllo documento Visualizza avvenga WPF di pesanti di scoprire come visualizzare questo particolare tipo di documento ViewModel. I modelli di dati effettivi per i documenti ViewModel sono in un dizionario risorse incluso dalla finestra principale di XAML.

Ho utilizzato l'approccio di gestione documenti ViewModel con WPF e Silverlight correttamente. Il codice di livello Visualizza solo è la scheda e ciò può essere verificato facilmente e quindi lasciato da solo. Questo approccio mantiene il ViewModel completamente indipendente della visualizzazione e ho in occasione di una commutati fornitori il componente a schede area di lavoro con solo modifiche minime nella classe adattatore e assolutamente Nessuna modifica ViewModel i modelli.

La possibilità di lavorare con documenti nel livello ViewModel ritiene elegante e implementazione ViewModel comandi come quello che illustrato qui è semplice. Le classi documento ViewModel anche diventano evidenti posizioni per esporre ICommand istanze correlate al documento.

Associa la visualizzazione in questi comandi e la bellezza di modello di progettazione MVVM luccica tramite. Inoltre, l'approccio di gestione documenti ViewModel funziona anche con l'approccio di singleton se occorre esporre dati prima che l'utente ha creato tutti i documenti (ad esempio in una finestra comprimibile strumento).

Conclusioni

Il modello di progettazione MVVM è un modello utile e potente, ma nessun modello di progettazione può risolvere ogni problema. Come ho illustrato qui, combinando il modello MVVM e obiettivi con altri modelli, come schede e singletons, sfruttando inoltre nuove funzionalità di .NET Framework 4, ad esempio invio dinamico, possono aiutare indirizzo numerosi problemi comuni intorno a implementare il MVVM progettazione motivo. Utilizzo MVVM in modo appropriato rende molto più elegante e gestibile di WPF e Silverlight applicazioni. Per ulteriori informazioni su MVVM, vedere Josh Smith articolo nel numero di febbraio 2009 di di MSDN Magazine.

Robert McCarter è uno sviluppatore software freelance Canadese, architetto e imprenditore. Leggere il suo blog all'indirizzo robertmccarter.wordpress.com .

Grazie all'esperto di tecnica seguente per la revisione di questo articolo:  Josh Smith