简体   繁体   English

Caliburn.Micro嵌套了ViewModels的最佳实践

[英]Caliburn.Micro nested ViewModels best practice

This is a pretty long question, so please bear with me. 这是一个很长的问题,所以请耐心等待。

Currently I am developing a small tool intended to help me keep track of the myriad of characters in my Stories. 目前我正在开发一个小工具,旨在帮助我跟踪我的故事中无数的角色。

The tool does the following: 该工具执行以下操作:

  • Load the characters which are currently stored as json on the disk and stores them in a list, which is presented in the Shell via a ListBox. 将当前存储为json的字符加载到磁盘上,并将它们存储在列表中,该列表通过ListBox在Shell中显示。
  • If the user then opens a character the Shell, which is a Conductor<Screen>.Collection.OneActive , opens a new CharacterViewModel , that derives from Screen . 如果用户随后打开一个字符,那么Shell是一个Conductor<Screen>.Collection.OneActive ,它将打开一个新的CharacterViewModel ,它派生自Screen
  • The Character gets the Character that is going to be opened via the IEventAggregator message system. Character获取将通过IEventAggregator消息系统打开的IEventAggregator
  • The CharacterViewModel furthermore has various properties which are sub ViewModels which bind to various sub Views. CharacterViewModel还具有各种属性,这些属性是子ViewModel,它们绑定到各种子视图。

And here is my Problem: Currently I initialize the sub ViewModels manually when the ChracterViewModel is initialized. 这里是我的问题:目前我手动初始化子的ViewModels当ChracterViewModel被初始化。 But this sounds fishy to me and I am pretty sure there is a better way to do this, but I cannot see how I should do it. 但这对我来说听起来很可疑,我很确定有更好的方法可以做到这一点,但我看不出应该怎么做。

Here is the code of the CharacterViewModel : 这是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;
    }
}

For Completion Sake I also give you one of the sub ViewModels. 对于完成清酒我也给你一个子ViewModels。 The others a of no importance because they are structured the same way, just perform different tasks. 其他一个并不重要,因为它们的结构相同,只是执行不同的任务。

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

I already read in on Screens, Conductors and Composition , IResult and Coroutines and skimmed the rest of the Documentation, but somehow I cannot find what I am looking for. 我已经阅读过Screens,Conductors and CompositionIResult和Coroutines并浏览了其余的文档,但不知怎的,我找不到我要找的东西。

//edit: I should mention the code I have works just fine. //编辑:我应该提一下我工作的代码就好了。 I'm just not satisfied with it, since I think I am not understanding the concept of MVVM quite right and therefore make faulty code. 我只是对它不满意,因为我认为我不太了解MVVM的概念,因此制作错误的代码。

There is nothing wrong with having one ViewModel instantiate several child ViewModels. 让一个ViewModel实例化几个子ViewModel没有任何问题。 If you're building a larger or more complex application, it's pretty much unavoidable if you want to keep your code readable and maintainable. 如果您正在构建更大或更复杂的应用程序,那么如果您希望保持代码的可读性和可维护性,这几乎是不可避免的。

In your example, you are instantiating all four child ViewModels whenever you create an instance of CharacterViewModel . 在您的示例中,每当您创建CharacterViewModel实例时,都要实例化所有四个子ViewModel。 Each of the child ViewModels takes IEventAggregator as a dependency. 每个子ViewModels都将IEventAggregator作为依赖项。 I would suggest that you treat those four child ViewModels as dependencies of the primary CharacterViewModel and import them through the constructor: 我建议您将这四个子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);
}

You can thus make the setters on the child ViewModel properties private. 因此,您可以将子ViewModel属性的setter设置为private。

Change your child ViewModels to import IEventAggregator through constructor injection: 更改您的子ViewModels以通过构造函数注入导入IEventAggregator

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

In your example, two of those child ViewModels are passed an instance of the Character data in their constructors, implying a dependency. 在您的示例中,其中两个子ViewModel在其构造函数中传递了Character数据的实例,这意味着依赖关系。 In these cases, I would give each child ViewModel a public Initialize() method where you set the Character data and activate the event aggregator subscription there: 在这些情况下,我会给每个子ViewModel一个公共的Initialize()方法,您可以在其中设置Character数据并在那里激活事件聚合器订阅:

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

Then call this method in your CharacterViewModel OnInitialize() method: 然后在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();
}

For the child ViewModels where you're only updating the Character data through the EventAggregator , leave the this.eventAggregator.Subscribe(this) call in the constructor. 对于只通过EventAggregator更新Character数据的子ViewModel,请在构造函数中保留this.eventAggregator.Subscribe(this)调用。

If any of your child ViewModels are not actually required for the page to function, you could initialize those VM properties via property import: 如果页面无法实际运行任何子ViewModel,则可以通过属性导入初始化这些VM属性:

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

Property imports don't occur until after the constructor has completed running. 在构造函数完成运行之后才会发生属性导入。

I would also suggest handling the instantiation of ICharacterSaveService through constructor injection as well, rather than explicitly creating a new instance every time you save data. 我还建议通过构造函数注入来处理ICharacterSaveService的实例化,而不是每次保存数据时显式创建新实例。

The primary purpose of MVVM was to allow front-end designers to work on the layout of the UI in a visual tool (Expression Blend) and coders to implement the behavior and business without interfering with one another. MVVM的主要目的是允许前端设计人员在可视化工具(Expression Blend)和编码器中处理UI的布局,以实现行为和业务而不会相互干扰。 The ViewModel exposes data to be bound to the view, describes the view's behavior at an abstract level, and frequently acts as a mediator to the back-end services. ViewModel公开要绑定到视图的数据,描述视图在抽象级别的行为,并经常充当后端服务的中介。

There is no one "correct" way to do it, and there are situations where it isn't the best solution. 没有一种“正确”的方法可以做到这一点,并且有些情况下它不是最好的解决方案。 There are times when the best solution is to toss the extra layer of abstraction of using a ViewModel and just write some code-behind. 有时最好的解决方案是使用ViewModel抛出额外的抽象层,然后编写一些代码隐藏。 So while it's a great structure for your application as a whole, don't fall into the trap of forcing everything to fit into the MVVM pattern. 因此,尽管它对于整个应用程序来说是一个很好的结构,但不要陷入强迫所有内容适合MVVM模式的陷阱。 If you have a few more graphically complex user controls where it simply works better to have some code-behind, then that's what you should do. 如果你有一些图形复杂的用户控件,它只是更好地有一些代码隐藏,那么这就是你应该做的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM