2014-09-03 24 views
6

Dies ist eine ziemlich lange Frage, also bitte bitte mit mir.Caliburn.Micro verschachtelt ViewModels Best Practice

Derzeit entwickle ich ein kleines Tool, das mir helfen soll, die unzähligen Charaktere in meinen Geschichten zu verfolgen.

Das Tool führt Folgendes aus:

  • Legen Sie die Zeichen, die zur Zeit als json auf der Festplatte und speichert sie in einer Liste gespeichert werden, die in der Shell über eine List-Box präsentiert wird.
  • Wenn der Benutzer dann ein Zeichen öffnet, öffnet die Shell, die eine Conductor<Screen>.Collection.OneActive ist, eine neue CharacterViewModel, die von Screen abgeleitet wird.
  • Die Character erhält das Zeichen, das über das Nachrichtensystem IEventAggregator geöffnet wird.
  • Die CharacterViewModel hat außerdem verschiedene Eigenschaften, die unter ViewModels sind, die an verschiedene Unteransichten binden.

Und hier ist mein Problem: Zur Zeit habe ich die Unterviewmodels manuell initialisiert werden, wenn der ChracterViewModel initialisiert wird. Aber das hört sich für mich fischig an und ich bin mir ziemlich sicher, dass es einen besseren Weg dafür gibt, aber ich kann nicht sehen, wie ich es tun soll. Hier

ist der Code des CharacterViewModel:

/// <summary>ViewModel for the character view.</summary> 
public class CharacterViewModel : Screen, IHandle<DataMessage<ICharacterTagsService>> 
{ 
    // -------------------------------------------------------------------------------------------------------------------- 
    // Fields 
    // ------------------------------------------------------------------------------------------------------------------- 

    /// <summary>The event aggregator.</summary> 
    private readonly IEventAggregator eventAggregator; 

    /// <summary>The character tags service.</summary> 
    private ICharacterTagsService characterTagsService; 

    // -------------------------------------------------------------------------------------------------------------------- 
    // Constructors & Destructors 
    // ------------------------------------------------------------------------------------------------------------------- 

    /// <summary>Initializes a new instance of the <see cref="CharacterViewModel"/> class.</summary> 
    public CharacterViewModel() 
    { 
     if (Execute.InDesignMode) 
     { 
      this.CharacterGeneralViewModel = new CharacterGeneralViewModel(); 

      this.CharacterMetadataViewModel = new CharacterMetadataViewModel(); 
     } 
    } 

    /// <summary>Initializes a new instance of the <see cref="CharacterViewModel"/> class.</summary> 
    /// <param name="eventAggregator">The event aggregator.</param> 
    [ImportingConstructor] 
    public CharacterViewModel(IEventAggregator eventAggregator) 
     : this() 
    { 
     this.eventAggregator = eventAggregator; 
     this.eventAggregator.Subscribe(this); 
    } 

    // -------------------------------------------------------------------------------------------------------------------- 
    // Properties 
    // ------------------------------------------------------------------------------------------------------------------- 

    /// <summary>Gets or sets the character.</summary> 
    public Character Character { get; set; } 

    /// <summary>Gets or sets the character general view model.</summary> 
    public CharacterGeneralViewModel CharacterGeneralViewModel { get; set; } 

    /// <summary>Gets or sets the character metadata view model.</summary> 
    public CharacterMetadataViewModel CharacterMetadataViewModel { get; set; } 

    /// <summary>Gets or sets the character characteristics view model.</summary> 
    public CharacterApperanceViewModel CharacterCharacteristicsViewModel { get; set; } 

    /// <summary>Gets or sets the character family view model.</summary> 
    public CharacterFamilyViewModel CharacterFamilyViewModel { get; set; } 

    // -------------------------------------------------------------------------------------------------------------------- 
    // Methods 
    // ------------------------------------------------------------------------------------------------------------------- 

    /// <summary>Saves a character to the file system as a json file.</summary> 
    public void SaveCharacter() 
    { 
     ICharacterSaveService saveService = new JsonCharacterSaveService(Constants.CharacterSavePathMyDocuments); 

     saveService.SaveCharacter(this.Character); 

     this.characterTagsService.AddTags(this.Character.Metadata.Tags); 
     this.characterTagsService.SaveTags(); 
    } 

    /// <summary>Called when initializing.</summary> 
    protected override void OnInitialize() 
    { 
     this.CharacterGeneralViewModel = new CharacterGeneralViewModel(this.eventAggregator); 
     this.CharacterMetadataViewModel = new CharacterMetadataViewModel(this.eventAggregator, this.Character); 
     this.CharacterCharacteristicsViewModel = new CharacterApperanceViewModel(this.eventAggregator, this.Character); 
     this.CharacterFamilyViewModel = new CharacterFamilyViewModel(this.eventAggregator); 

     this.eventAggregator.PublishOnUIThread(new CharacterMessage 
     { 
      Data = this.Character 
     }); 


     base.OnInitialize(); 
    } 

    /// <summary> 
    /// Handles the message. 
    /// </summary> 
    /// <param name="message">The message.</param> 
    public void Handle(DataMessage<ICharacterTagsService> message) 
    { 
     this.characterTagsService = message.Data; 
    } 
} 

für die Fertigstellung Sake ich Ihnen auch eine der Unterviewmodels geben. Die anderen haben keine Bedeutung, da sie auf die gleiche Weise strukturiert sind, nur verschiedene Aufgaben ausführen.

/// <summary>The character metadata view model.</summary> 
public class CharacterMetadataViewModel : Screen 
{ 
    /// <summary>The event aggregator.</summary> 
    private readonly IEventAggregator eventAggregator; 

    /// <summary>Initializes a new instance of the <see cref="CharacterMetadataViewModel"/> class.</summary> 
    public CharacterMetadataViewModel() 
    { 
     if (Execute.InDesignMode) 
     { 
      this.Character = DesignData.LoadSampleCharacter(); 
     } 
    } 

    /// <summary>Initializes a new instance of the <see cref="CharacterMetadataViewModel"/> class.</summary> 
    /// <param name="eventAggregator">The event aggregator.</param> 
    /// <param name="character">The character.</param> 
    public CharacterMetadataViewModel(IEventAggregator eventAggregator, Character character) 
    { 
     this.Character = character; 

     this.eventAggregator = eventAggregator; 
     this.eventAggregator.Subscribe(this); 
    } 

    /// <summary>Gets or sets the character.</summary> 
    public Character Character { get; set; } 

    /// <summary> 
    /// Gets or sets the characters tags. 
    /// </summary> 
    public string Tags 
    { 
     get 
     { 
      return string.Join("; ", this.Character.Metadata.Tags); 
     } 

     set 
     { 
      char[] delimiters = { ',', ';', ' ' }; 

      List<string> tags = value.Split(delimiters, StringSplitOptions.RemoveEmptyEntries).ToList(); 

      this.Character.Metadata.Tags = tags; 
      this.NotifyOfPropertyChange(() => this.Tags); 
     } 
    } 
} 

Ich las bereits in auf Screens, Conductors and Composition, IResult and Coroutines und den Rest der Dokumentation abgeschöpft, aber irgendwie kann ich nicht finden, was ich suche.

// bearbeiten: Ich sollte den Code erwähnen, ich habe funktioniert gut. Ich bin einfach nicht damit zufrieden, da ich denke, ich verstehe das Konzept von MVVM nicht ganz richtig und mache daher fehlerhaften Code.

+1

Es ist ganz normal, dass ein Ansichtsmodell ein oder mehrere andere Ansichtsmodelle instanziiert. – Sheridan

Antwort

7

Es ist nichts falsch daran, dass ein ViewModel mehrere untergeordnete ViewModels instanziiert. Wenn Sie eine größere oder komplexere Anwendung erstellen, ist es ziemlich unvermeidlich, wenn Sie Ihren Code lesbar und wartbar halten möchten.

In Ihrem Beispiel instanziieren Sie alle vier untergeordneten ViewModels, wenn Sie eine Instanz von CharacterViewModel erstellen. Jedes der untergeordneten ViewModels verwendet IEventAggregator als Abhängigkeit. Ich würde vorschlagen, dass Sie diese vier Kind Viewmodels als Abhängigkeiten des primären CharacterViewModel und importieren sie durch den Konstruktor behandeln:

[ImportingConstructor] 
public CharacterViewModel(IEventAggregator eventAggregator, 
          CharacterGeneralViewModel generalViewModel, 
          CharacterMetadataViewModel metadataViewModel, 
          CharacterAppearanceViewModel appearanceViewModel, 
          CharacterFamilyViewModel familyViewModel) 
{ 
    this.eventAggregator = eventAggregator; 
    this.CharacterGeneralViewModel generalViewModel; 
    this.CharacterMetadataViewModel = metadataViewModel; 
    this.CharacterCharacteristicsViewModel = apperanceViewModel; 
    this.CharacterFamilyViewModel = familyViewModel; 

    this.eventAggregator.Subscribe(this); 
} 

Sie damit die Setter auf das Kind Ansichtsmodell Eigenschaften privat machen können.

Ihr Kind Viewmodel ändern IEventAggregator durch Konstruktor Injektion zu importieren:

[ImportingConstructor] 
public CharacterGeneralViewModel(IEventAggregator eventAggregator) 
{ 
    this.eventAggregator = eventAggregator; 
} 

In Ihrem Beispiel zwei dieses Kindes Viewmodel eine Instanz der Character Daten übergeben werden, in ihren Konstrukteuren, eine Abhängigkeit impliziert. In diesen Fällen würde ich jedes Kind Ansichtsmodell eine öffentliche Initialize() Methode geben, wo Sie die Character Daten und aktivieren Sie die Ereignis-Aggregator Abonnement dort eingestellt:

public Initialize(Character character) 
{ 
    this.Character = character; 
    this.eventAggregator.Subscribe(this); 
} 

Dann diese Methode aufrufen in Ihrem CharacterViewModelOnInitialize() Methode:

protected override void OnInitialize() 
{  
    this.CharacterMetadataViewModel.Initialize(this.Character); 
    this.CharacterCharacteristicsViewModel.Initialize(this.Character);  

    this.eventAggregator.PublishOnUIThread(new CharacterMessage 
    { 
     Data = this.Character 
    }); 


    base.OnInitialize(); 
} 

Für die untergeordneten ViewModels, in denen Sie nur die Daten Character über die EventAggregator aktualisieren, lassen Sie den Aufruf this.eventAggregator.Subscribe(this) im Konstruktor.

Wenn einer Ihrer Kind Viewmodels sind eigentlich nicht für die Seite erforderlich ist, um zu funktionieren, können Sie diese VM-Eigenschaften über Immobilien-Import nicht initialisieren:

[Import] 
public CharacterGeneralViewModel CharacterGeneralViewModel { get; set; } 

Property Importe treten erst nach der Konstruktor Lauf abgeschlossen .

Ich würde auch vorschlagen, die Instanziierung von ICharacterSaveService durch Konstruktorinjektion zu behandeln, anstatt explizit eine neue Instanz jedes Mal zu erstellen, wenn Sie Daten speichern. Der Hauptzweck von MVVM bestand darin, Front-End-Designern die Arbeit am Layout der Benutzeroberfläche in einem visuellen Tool (Expression Blend) und Codierern zu ermöglichen, um das Verhalten und das Geschäft zu implementieren, ohne sich gegenseitig zu stören. Das ViewModel stellt Daten bereit, die an die Ansicht gebunden werden sollen, beschreibt das Verhalten der Ansicht auf einer abstrakten Ebene und agiert häufig als Vermittler für die Back-End-Dienste.

Es gibt keinen "richtigen" Weg, es zu tun, und es gibt Situationen, in denen es nicht die beste Lösung ist. Es gibt Zeiten, in denen die beste Lösung darin besteht, die zusätzliche Ebene der Abstraktion mit einem ViewModel zu umgehen und einfach Code-Behind zu schreiben. Während es eine großartige Struktur für Ihre Anwendung als Ganzes ist, fallen Sie nicht in die Falle, alles zu zwingen, in das MVVM-Muster zu passen. Wenn Sie ein paar grafisch komplexere Benutzersteuerelemente haben, bei denen es einfach besser funktioniert, etwas Code-Behind zu haben, dann sollten Sie das tun.

+1

Habe gerade bemerkt, dass diese Frage 5 Monate alt ist. Hoppla. Naja, hoffentlich wird meine Antwort für jemanden noch hilfreich sein. – TeagansDad

+1

Ja hat es tatsächlich. Einige Punkte, die ich bereits angesprochen habe (der Speicherdienst verwendet jetzt das Repository-Muster), aber insgesamt muss ich mich bedanken! – Ruhrpottpatriot