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

Ruh*_*iot 8 c# wpf mvvm caliburn.micro

这是一个很长的问题,所以请耐心等待.

目前我正在开发一个小工具,旨在帮助我跟踪我的故事中无数的角色.

该工具执行以下操作:

  • 将当前存储为json的字符加载到磁盘上,并将它们存储在列表中,该列表通过ListBox在Shell中显示.
  • 如果用户随后打开一个字符,则Shell(即a Conductor<Screen>.Collection.OneActive)会打开一个新的CharacterViewModel,派生自Screen.
  • Character获取即将通过要打开的汉字IEventAggregator信息系统.
  • CharacterViewModel还具有其是结合至各个子查看子的ViewModels各种属性.

这是我的问题:目前我在初始化时手动初始化子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;
    }
}
Run Code Online (Sandbox Code Playgroud)

对于完成清酒我也给你一个子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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经阅读过Screens,Conductors and Composition,IResult和Coroutines,并浏览了其余的文档,但不知怎的,我找不到我要找的东西.

//编辑:我应该提一下我工作的代码就好了.我只是对它不满意,因为我认为我不太了解MVVM的概念,因此制作错误的代码.

Tea*_*Dad 10

让一个ViewModel实例化几个子ViewModel没有任何问题.如果您正在构建更大或更复杂的应用程序,那么如果您希望保持代码的可读性和可维护性,这几乎是不可避免的.

在您的示例中,无论何时创建实例,都要实例化所有四个子ViewModel CharacterViewModel.每个子ViewModel都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);
}
Run Code Online (Sandbox Code Playgroud)

因此,您可以将子ViewModel属性的setter设置为private.

将您的子ViewModel更改为IEventAggregator通过构造函数注入导入:

[ImportingConstructor]
public CharacterGeneralViewModel(IEventAggregator eventAggregator)
{
    this.eventAggregator = eventAggregator;
}
Run Code Online (Sandbox Code Playgroud)

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

public Initialize(Character character)
{
    this.Character = character;
    this.eventAggregator.Subscribe(this);   
}
Run Code Online (Sandbox Code Playgroud)

然后在您的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();
}
Run Code Online (Sandbox Code Playgroud)

对于只Character通过其更新数据的子ViewModel,请在构造函数中EventAggregator保留this.eventAggregator.Subscribe(this)调用.

如果页面无法实际运行任何子ViewModel,则可以通过属性导入初始化这些VM属性:

[Import]
public CharacterGeneralViewModel CharacterGeneralViewModel { get; set; }
Run Code Online (Sandbox Code Playgroud)

在构造函数完成运行之后才会发生属性导入.

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

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

没有一种"正确"的方法可以做到这一点,并且有些情况下它不是最好的解决方案.有时最好的解决方案是使用ViewModel抛出额外的抽象层,然后编写一些代码隐藏.因此,尽管它对于整个应用程序来说是一个很好的结构,但不要陷入强迫所有内容适合MVVM模式的陷阱.如果你有一些图形复杂的用户控件,它只是更好地有一些代码隐藏,那么这就是你应该做的.

  • 刚刚注意到这个问题是5个月大了.哎呀.哦,希望我的回答仍然对某人有帮助. (2认同)