2014-09-03 77 views
6

這是一個很長的問題,所以請耐心等待。Caliburn.Micro嵌套ViewModels最佳實踐

目前我正在開發一個小工具,旨在幫助我跟蹤故事中無數的角色。

該工具執行以下操作:

  • 負載當前存儲爲列表中,這是在殼經由一個列表框呈現的磁盤,並將它們存儲在JSON的字符。
  • 如果用戶隨後打開一個字符,則爲Conductor<Screen>.Collection.OneActive的外殼會打開一個新的CharacterViewModel,該派生自該字符。
  • Character獲取將通過IEventAggregator消息系統打開的字符。
  • CharacterViewModel還具有各種屬性,它們是綁定到各種子視圖的子視圖模型。

這裏是我的問題: 目前我手動初始化子的ViewModels當ChracterViewModel被初始化。但是,這聽起來很腥,我很確定有更好的方法來做到這一點,但我不明白我該怎麼做。

這裏是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; 
    } 
} 

完成的緣故,我也給你分的ViewModels之一。其他人不重要,因爲他們的結構相同,只是執行不同的任務。

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

我已經讀上Screens, Conductors and CompositionIResult and Coroutines和脫脂文檔的其餘部分,但不知何故,我無法找到我所期待的。

//編輯:我應該提到我的代碼工作得很好。我只是不滿意它,因爲我認爲我不理解MVVM的概念是非常正確的,因此做出錯誤的代碼。

+1

有一個視圖模型實例化一個或多個其他視圖模型是很正常的。 – Sheridan 2014-09-03 19:49:19

回答

7

讓一個ViewModel實例化幾個子ViewModel沒有什麼問題。如果你正在構建一個更大或更復雜的應用程序,如果你想保持你的代碼的可讀性和可維護性,這是非常不可避免的。

在您的示例中,您將在創建CharacterViewModel的實例時實例化所有四個子ViewModels。每個子ViewModel都需要IEventAggregator作爲依賴項。我建議你治療那些四個子的ViewModels作爲主CharacterViewModel的依賴關係,並通過構造導入:

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

可以從而使對孩子視圖模型性能私人的制定者。

改變你的孩子的ViewModels通過構造函數注入導入IEventAggregator

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

在您的例子,其中的兩個孩子的ViewModels在它們的構造函數傳遞的Character數據的實例,這意味着依賴。在這種情況下,我想給每個孩子視圖模型公開Initialize()方法你設置Character數據,並激活事件聚合訂閱有:

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

然後調用這個方法在你CharacterViewModelOnInitialize()方法:

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

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


    base.OnInitialize(); 
} 

對於孩子的ViewModels在那裏你只能通過EventAggregator更新Character數據,留在構造函數中調用this.eventAggregator.Subscribe(this)

如果您的孩子的ViewModels都沒有實際需要的頁面的功能,你可以通過初始化屬性導入這些VM屬性:

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

物業進口不發生後才構造完成運行。

我也建議通過構造函數注入處理ICharacterSaveService的實例化,而不是每次保存數據時都顯式創建一個新實例。

MVVM的主要目的是允許前端設計人員在可視工具(Expression Blend)和編碼器中對UI的佈局進行工作,以實現行爲和業務而不會相互干擾。 ViewModel公開要綁定到視圖的數據,在抽象級別描述視圖的行爲,並且經常充當後端服務的中介。

沒有一種「正確」的方式來做到這一點,並且在某些情況下它不是最好的解決方案。有時候最好的解決方案是拋棄額外的使用ViewModel的抽象層,而只是寫一些代碼隱藏。因此,儘管它對於整個應用程序來說是一個很好的結構,但不要陷入強迫所有東西適合MVVM模式的陷阱。如果你有一些更復雜的圖形用戶控件,它只需要更好的代碼隱藏功能,那麼這就是你應該做的。

+1

現在才注意到這個問題是5個月大。哎呀。噢,希望我的回答對某人仍然有幫助。 – TeagansDad 2015-02-20 19:00:33

+1

是的,它實際上有。我已經解決了一些問題(保存服務現在使用存儲庫模式),但總的來說,我必須說謝謝! – Ruhrpottpatriot 2015-02-20 22:54:21