簡體   English   中英

Caliburn.Micro嵌套了ViewModels的最佳實踐

[英]Caliburn.Micro nested ViewModels best practice

這是一個很長的問題,所以請耐心等待。

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

該工具執行以下操作:

  • 將當前存儲為json的字符加載到磁盤上,並將它們存儲在列表中,該列表通過ListBox在Shell中顯示。
  • 如果用戶隨后打開一個字符,那么Shell是一個Conductor<Screen>.Collection.OneActive ,它將打開一個新的CharacterViewModel ,它派生自Screen
  • Character獲取將通過IEventAggregator消息系統打開的IEventAggregator
  • CharacterViewModel還具有各種屬性,這些屬性是子ViewModel,它們綁定到各種子視圖。

這里是我的問題:目前我手動初始化子的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和Coroutines並瀏覽了其余的文檔,但不知怎的,我找不到我要找的東西。

//編輯:我應該提一下我工作的代碼就好了。 我只是對它不滿意,因為我認為我不太了解MVVM的概念,因此制作錯誤的代碼。

讓一個ViewModel實例化幾個子ViewModel沒有任何問題。 如果您正在構建更大或更復雜的應用程序,那么如果您希望保持代碼的可讀性和可維護性,這幾乎是不可避免的。

在您的示例中,每當您創建CharacterViewModel實例時,都要實例化所有四個子ViewModel。 每個子ViewModels都將IEventAggregator作為依賴項。 我建議您將這四個子ViewModel視為主要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);
}

因此,您可以將子ViewModel屬性的setter設置為private。

更改您的子ViewModels以通過構造函數注入導入IEventAggregator

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

在您的示例中,其中兩個子ViewModel在其構造函數中傳遞了Character數據的實例,這意味着依賴關系。 在這些情況下,我會給每個子ViewModel一個公共的Initialize()方法,您可以在其中設置Character數據並在那里激活事件聚合器訂閱:

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

然后在CharacterViewModel OnInitialize()方法中調用此方法:

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

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


    base.OnInitialize();
}

對於只通過EventAggregator更新Character數據的子ViewModel,請在構造函數中保留this.eventAggregator.Subscribe(this)調用。

如果頁面無法實際運行任何子ViewModel,則可以通過屬性導入初始化這些VM屬性:

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

在構造函數完成運行之后才會發生屬性導入。

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

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

沒有一種“正確”的方法可以做到這一點,並且有些情況下它不是最好的解決方案。 有時最好的解決方案是使用ViewModel拋出額外的抽象層,然后編寫一些代碼隱藏。 因此,盡管它對於整個應用程序來說是一個很好的結構,但不要陷入強迫所有內容適合MVVM模式的陷阱。 如果你有一些圖形復雜的用戶控件,它只是更好地有一些代碼隱藏,那么這就是你應該做的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM