Caliburn.Micro嵌套ViewModels最佳实践 [英] Caliburn.Micro nested ViewModels best practice

查看:80
本文介绍了Caliburn.Micro嵌套ViewModels最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个很长的问题,所以请忍受我.

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.

该工具执行以下操作:

  • 将当前存储为json的字符加载到磁盘上,并将它们存储在列表中,该列表通过列表框显示在Shell中.
  • 如果用户随后打开一个字符,则外壳程序(即Conductor<Screen>.Collection.OneActive)将打开一个新的CharacterViewModel,它是从Screen派生的.
  • Character获取将要通过IEventAggregator消息系统打开的角色.
  • CharacterViewModel还具有各种属性,这些属性是绑定到各种子视图的子ViewModel.
  • 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.
  • If the user then opens a character the Shell, which is a Conductor<Screen>.Collection.OneActive, opens a new CharacterViewModel, that derives from Screen.
  • The Character gets the Character that is going to be opened via the IEventAggregator message system.
  • The CharacterViewModel furthermore has various properties which are sub ViewModels which bind to various sub Views.

这是我的问题: 当前,在初始化ChracterViewModel时,我会手动初始化子ViewModels.但这听起来像是我的鱼,我很确定有更好的方法可以做到这一点,但是我不知道该怎么做.

And here is my Problem: Currently I initialize the sub ViewModels manually when the ChracterViewModel is initialized. 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.

这是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. 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.

//edit:我应该提到我的代码可以正常工作.我只是对它不满意,因为我认为我不太了解MVVM的概念,因此编写了错误的代码.

//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.

推荐答案

让一个ViewModel实例化多个子ViewModel并没有错.如果您要构建更大或更复杂的应用程序,则要保持代码的可读性和可维护性是不可避免的.

There is nothing wrong with having one ViewModel instantiate several child ViewModels. 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.

在您的示例中,每当创建CharacterViewModel的实例时,您都将实例化所有四个子ViewModel.每个子ViewModels都将IEventAggregator作为依赖项.我建议您将这四个子ViewModel视为主要CharacterViewModel的依赖项,并通过构造函数将其导入:

In your example, you are instantiating all four child ViewModels whenever you create an instance of CharacterViewModel. Each of the child ViewModels takes IEventAggregator as a dependency. I would suggest that you treat those four child ViewModels as dependencies of the primary CharacterViewModel and import them through the constructor:

[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属性上的设置器设为私有.

You can thus make the setters on the child ViewModel properties private.

更改子ViewModels以通过构造函数注入导入IEventAggregator:

Change your child ViewModels to import IEventAggregator through constructor injection:

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

在您的示例中,这些子ViewModel中的两个在其构造函数中传递了Character数据的实例,这意味着依赖.在这些情况下,我将为每个子ViewModel提供一个公共的Initialize()方法,您可以在其中设置Character数据并在那里激活事件聚合器订阅:

In your example, two of those child ViewModels are passed an instance of the Character data in their constructors, implying a dependency. 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:

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

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

Then call this method in your CharacterViewModel OnInitialize() method:

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数据的子ViewModels,将this.eventAggregator.Subscribe(this)调用保留在构造函数中.

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.

如果页面正常运行实际上不需要您的任何子ViewModel,则可以通过属性导入来初始化这些VM属性:

If any of your child ViewModels are not actually required for the page to function, you could initialize those VM properties via property import:

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

直到构造函数完成运行之后,才进行属性导入.

Property imports don't occur until after the constructor has completed running.

我还建议也通过构造函数注入处理ICharacterSaveService的实例化,而不是每次保存数据时都显式创建一个新实例.

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.

MVVM的主要目的是允许前端设计人员使用可视化工具(Expression Blend)来处理UI的布局,并允许编码人员在不相互干扰的情况下实现行为和业务. ViewModel公开要绑定到视图的数据,以抽象级别描述视图的行为,并经常充当后端服务的中介.

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. 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的额外抽象层,然后编写一些代码隐藏.因此,尽管它对于您的整个应用程序来说是一个很好的结构,但不要陷入强迫所有内容都适合MVVM模式的陷阱.如果您还有一些图形复杂的用户控件,在其中放置一些代码后效果更好,那么这就是您应该做的.

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. 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. 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.

这篇关于Caliburn.Micro嵌套ViewModels最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆