2014-09-03 21 views
6

To dość długie pytanie, więc proszę o zachowanie mnie.Caliburn.Micro zagnieżdżona najlepsza praktyka ViewModels

Obecnie opracowuję małe narzędzie, które ma mi pomóc śledzić niezliczone postacie w moich Historiach.

Narzędzie wykonuje następujące operacje:

  • obciążenia znaki, które są obecnie przechowywane jako json na dysku i zapisuje je na liście, która zostanie przedstawiona w Shell poprzez ListBox.
  • Jeśli użytkownik otworzy następnie znak, Shell, który jest Conductor<Screen>.Collection.OneActive, otwiera nowy CharacterViewModel, który pochodzi z Screen.
  • Znak Character otrzymuje znak, który zostanie otwarty za pośrednictwem systemu komunikatów IEventAggregator.
  • Ponadto, CharacterViewModel ma różne właściwości, które są pod ViewModels, które wiążą różne widoki Sub.

I tu jest mój problem: Obecnie zainicjować ViewModels sub ręcznie gdy ChracterViewModel jest inicjowany. Ale brzmi to dla mnie podejrzanie i jestem pewien, że jest lepszy sposób, aby to zrobić, ale nie widzę, jak powinienem to zrobić.

Oto kod 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; 
    } 
} 

REALIZACJI Sake ja również daje jedną z ViewModels podrzędnych. Inni nie mają znaczenia, ponieważ są zorganizowani w ten sam sposób, po prostu wykonują różne zadania.

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

już czytać na Screens, Conductors and Composition, IResult and Coroutines i odtłuszczonego resztę dokumentacji, ale jakoś nie mogę znaleźć to, czego szukam.

// edit: Powinienem wspomnieć o kodzie, który mam działa dobrze. Po prostu nie jestem z tego zadowolony, ponieważ uważam, że nie rozumiem idei MVVM całkiem dobrze i dlatego tworzę wadliwy kod.

+1

To całkiem normalne, że jeden model widoku tworzy jeden lub więcej innych modeli widoku. – Sheridan

Odpowiedz

7

Nie ma nic złego w tym, że jeden egzemplarz ViewModel tworzy kilka potomnych ViewModels. Jeśli tworzysz większą lub bardziej złożoną aplikację, jest to prawie nieuniknione, jeśli chcesz, aby Twój kod był czytelny i łatwy w utrzymaniu.

W tym przykładzie tworzysz instancję wszystkich czterech potomnych ViewModels za każdym razem, gdy tworzysz instancję CharacterViewModel. Każdy z podrzędnych ViewModels pobiera IEventAggregator jako zależność. Proponuję, żeby traktować te cztery ViewModels potomnych jako zależności od pierwotnej CharacterViewModel i importować je przez konstruktora:

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

Można zatem dokonać ustawiające na dziecko właściwości ViewModel prywatnego.

Zmień ViewModels potomnych importować IEventAggregator poprzez wstrzyknięcie konstruktora:

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

w przykładzie, dwa z tych dziecka ViewModels są przekazywane instancję danych Character w ich konstruktorów, co sugeruje zależność. W tych przypadkach, dałbym każdemu dziecku ViewModel publicznego Initialize() metodę gdzie można ustawić dane Character i aktywować subskrypcję zbiorczej wydarzenie tam:

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

następnie wywołać tę metodę w swojej CharacterViewModelOnInitialize() metoda:

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

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


    base.OnInitialize(); 
} 

Dla dzieci ViewModels, dla których dane są aktualizowane tylko przez Character, należy wywołać wywołanie this.eventAggregator.Subscribe(this) w konstruktorze.

Jeżeli którykolwiek z ViewModels dzieci nie są rzeczywiście potrzebne do funkcjonowania strony, można zainicjować te właściwości VM poprzez import własności:

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

import Property nie występują dopiero po konstruktor ukończył bieg .

Sugerowałbym także obsługę instancji ICharacterSaveService również za pomocą iniekcji konstruktora, zamiast jawnie tworzyć nową instancję za każdym razem, gdy zapisujesz dane.

Głównym celem MVVM było umożliwienie projektantom front-end pracy nad układem interfejsu użytkownika w narzędziu wizualnym (Expression Blend) i programistach w celu implementacji zachowania i biznesu bez wzajemnego zakłócania się. ViewModel udostępnia dane związane z widokiem, opisuje zachowanie widoku na poziomie abstrakcyjnym i często działa jako mediator w usługach zaplecza.

Nie ma jednego "właściwego" sposobu, aby to zrobić, a są sytuacje, w których nie jest to najlepsze rozwiązanie. Są chwile, w których najlepszym rozwiązaniem jest rzucenie dodatkowej warstwy abstrakcji za pomocą ViewModel i napisanie kodu za tyłem. Tak więc, chociaż jest to świetna struktura dla całej aplikacji, nie wpadnij w pułapkę wymuszania wszystkiego, aby dopasować się do wzorca MVVM. Jeśli masz kilka bardziej złożonych graficznie kontrolek użytkownika, gdzie po prostu działa lepiej, aby mieć trochę za sobą kodu, to właśnie powinieneś zrobić.

+1

Właśnie zauważyłem, że to pytanie ma 5 miesięcy. Ups. No cóż, mam nadzieję, że moja odpowiedź będzie nadal pomocna dla kogoś. – TeagansDad

+1

Tak, rzeczywiście tak. Niektóre punkty, które już omówiłem (usługa zapisu teraz używa wzorca repozytorium), ale ogólnie muszę podziękować! – Ruhrpottpatriot