【问题标题】:Caliburn.Micro nested ViewModels best practiceCaliburn.Micro 嵌套 ViewModels 最佳实践
【发布时间】:2014-09-03 16:59:05
【问题描述】:

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

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

该工具执行以下操作:

  • 加载当前在磁盘上存储为 json 的字符,并将它们存储在一个列表中,该列表通过 ListBox 呈现在 Shell 中。
  • 如果用户随后打开一个字符,Shell(即Conductor<Screen>.Collection.OneActive)会打开一个新的CharacterViewModel,该CharacterViewModel 派生自Screen
  • Character 获取要通过IEventAggregator 消息系统打开的字符。
  • CharacterViewModel 还具有各种属性,它们是绑定到各种子视图的子视图模型。

这是我的问题: 目前我在初始化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;
    }
}

为了完成,我还给你一个子 ViewModel。其他的并不重要,因为它们的结构相同,只是执行不同的任务。

/// <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 的概念的理解并不完全正确,因此编写了错误的代码。

【问题讨论】:

  • 一个视图模型实例化一个或多个其他视图模型是很正常的。

标签: c# wpf mvvm caliburn.micro


【解决方案1】:

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

在您的示例中,每当您创建 CharacterViewModel 的实例时,您都会实例化所有四个子 ViewModel。每个子 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);
}

因此,您可以将子 ViewModel 属性上的设置器设为私有。

将您的子 ViewModel 更改为通过构造函数注入导入 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);   
}

然后在你的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();
}

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

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

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

在构造函数完成运行之前不会发生属性导入。

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

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

没有一种“正确”的方法可以做到这一点,并且在某些情况下它不是最佳解决方案。有时最好的解决方案是放弃使用 ViewModel 的额外抽象层,只编写一些代码隐藏。因此,虽然它对于整个应用程序来说是一个很好的结构,但不要陷入强迫一切​​都适应 MVVM 模式的陷阱。如果您有一些图形更复杂的用户控件,其中一些代码隐藏效果更好,那么您应该这样做。

【讨论】:

  • 刚刚注意到这个问题已经 5 个月大了。哎呀。哦,好吧,希望我的回答仍然对某人有所帮助。
  • 是的,它确实有。我已经解决了一些问题(保存服务现在使用存储库模式),但总的来说我不得不说谢谢!
  • 今天仍然有用。有任何建议 @TeagansDad 以了解有关 Caliburn Micro 的更多信息吗?我已经浏览了这些文档,但它们并不是很有帮助,至少对于像我这样的菜鸟而言。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-02
  • 2011-07-12
  • 2023-03-23
  • 2017-12-12
  • 2021-03-26
  • 2019-03-16
相关资源
最近更新 更多