Caliburn.Micro 嵌套 ViewModel 最佳实践

2023-11-25

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

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

该工具执行以下操作:

  • 加载当前以 json 形式存储在磁盘上的字符并将它们存储在列表中,该列表通过 ListBox 在 Shell 中呈现。
  • 如果用户随后打开一个字符 Shell,它是一个Conductor<Screen>.Collection.OneActive,打开一个新的CharacterViewModel,源自Screen.
  • The Character获取将要通过以下方式打开的角色IEventAggregator消息系统。
  • The CharacterViewModel此外还有各种属性,它们是绑定到各种子视图的子视图模型。

这是我的问题: 目前,我在以下情况下手动初始化子 ViewModel: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);
        }
    }
}

我已经读过屏幕、导体和构图, IResult 和协程并浏览了文档的其余部分,但不知何故我找不到我要找的东西。

//编辑:我应该提到我的代码工作得很好。我只是对此不满意,因为我认为我没有正确理解 MVVM 的概念,因此编写了错误的代码。


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

在您的示例中,每当您创建一个实例时,您都会实例化所有四个子 ViewModelCharacterViewModel。每个子 ViewModel 都采用IEventAggregator作为依赖。我建议您将这四个子 ViewModel 视为主 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 属性的设置器设为私有。

更改要导入的子 ViewModelIEventAggregator通过构造函数注入:

[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() 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();
}

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

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

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

直到构造函数完成运行后才会发生属性导入。

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Caliburn.Micro 嵌套 ViewModel 最佳实践 的相关文章

  • C 编程 - 文件 - fwrite

    我有一个关于编程和文件的问题 while current NULL if current gt Id Doctor 0 current current gt next id doc current gt Id Doctor if curre
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 类模板参数推导 - clang 和 gcc 不同

    下面的代码使用 gcc 编译 但不使用 clang 编译 https godbolt org z ttqGuL template
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • 不同枚举类型的范围和可转换性

    在什么条件下可以从一种枚举类型转换为另一种枚举类型 让我们考虑以下代码 include
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • 将多个表映射到实体框架中的单个实体类

    我正在开发一个旧数据库 该数据库有 2 个具有 1 1 关系的表 目前 我为每个定义的表定义了一种类型 1Test 1Result 我想将这些特定的表合并到一个类中 当前的类型如下所示 public class Result public
  • 显示UnityWebRequest的进度

    我正在尝试使用下载 assetbundle统一网络请求 https docs unity3d com ScriptReference Networking UnityWebRequest GetAssetBundle html并显示进度 根
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 如何在整个 ASP .NET MVC 应用程序中需要授权

    我创建的应用程序中 除了启用登录的操作之外的每个操作都应该超出未登录用户的限制 我应该添加 Authorize 每个班级标题前的注释 像这儿 namespace WebApplication2 Controllers Authorize p
  • 如何使用 C# / .Net 将文件列表从 AWS S3 下载到我的设备?

    我希望下载存储在 S3 中的多个图像 但目前如果我只能下载一个就足够了 我有对象路径的信息 当我运行以下代码时 出现此错误 遇到错误 消息 读取对象时 访问被拒绝 我首先做一个亚马逊S3客户端基于我的密钥和访问配置的对象连接到服务器 然后创
  • 通过指向其基址的指针删除 POD 对象是否安全?

    事实上 我正在考虑那些微不足道的可破坏物体 而不仅仅是POD http en wikipedia org wiki Plain old data structure 我不确定 POD 是否可以有基类 当我读到这个解释时is triviall
  • 如何在Xamarin中删除ViewTreeObserver?

    假设我需要获取并设置视图的高度 在 Android 中 众所周知 只有在绘制视图之后才能获取视图高度 如果您使用 Java 有很多答案 最著名的方法之一如下 取自这个答案 https stackoverflow com a 24035591
  • 是否可以在 .NET Core 中将 gRPC 与 HTTP/1.1 结合使用?

    我有两个网络服务 gRPC 客户端和 gRPC 服务器 服务器是用 NET Core编写的 然而 客户端是托管在 IIS 8 5 上的 NET Framework 4 7 2 Web 应用程序 所以它只支持HTTP 1 1 https le
  • Windows 和 Linux 上的线程

    我在互联网上看到过在 Windows 上使用 C 制作多线程应用程序的教程 以及在 Linux 上执行相同操作的其他教程 但不能同时用于两者 是否存在即使在 Linux 或 Windows 上编译也能工作的函数 您需要使用一个包含两者的实现
  • 如何在文本框中插入图像

    有没有办法在文本框中插入图像 我正在开发一个聊天应用程序 我想用图标图像更改值 等 但我找不到如何在文本框中插入图像 Thanks 如果您使用 RichTextBox 进行聊天 请查看Paste http msdn microsoft co
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new

随机推荐

  • MySQL ORDER BY IN()

    我有一个 PHP 数组 其中包含多个 ID 这些号码已经被订购 现在我想通过 IN 方法获取结果 以获取所有 ID 但是 这些 ID 应该像 IN 方法中那样排序 例如 IN 4 7 3 8 9 应该给出如下结果 4 Article 4 7
  • 如何设置 VSCode 在 C# 和 C++ 中键入时将大括号放在新行上?

    我希望 VS Code 在 C 和 C 中将花括号放在新行上 How it works now How it should look 尝试了 C FixFormat 扩展 但只有在按下 CTRL K F 后它才起作用 但我希望 VS Cod
  • 致命异常:java.lang.NoClassDefFoundError:rt

    Fatal Exception java lang NoClassDefFoundError rt at rs SourceFile 17 at android support v7 widget RecyclerView onSaveIn
  • iOS 7 联系电话空格不是空格[重复]

    这个问题在这里已经有答案了 在我的应用程序中 我尝试检索联系人号码列表并尝试对它们进行操作 我意识到每当我添加新联系人 更新到 iOS 7 后 新联系人格式都会发生变化 因为新添加的号码中有空格 使用普通的替换方法不会删除空格 这些真的是空
  • 来自 hbase/filesystem 的 hadoop namenode 连接中的 EOF 异常是什么意思?

    这既是关于java EOF异常的一般问题 也是与jar互操作性相关的Hadoop的EOF异常 关于任一主题的评论和答案都是可以接受的 背景 我注意到一些讨论神秘异常的线程 该异常最终是由 readInt 方法引起的 此异常似乎具有一些独立于
  • sp_send_dbmail 附加在数据库中存储为 varbinary 的文件

    我有一个由两部分组成的问题 涉及使用 sp send dbmail 将查询结果作为附件发送 问题一 仅会打开基本的 txt 文件 任何其他格式 例如 pdf 或 jpg 都会损坏 问题2 当尝试发送多个附件时 我收到一个文件 其中所有文件名
  • 在没有命名空间但在需要命名空间的类中反序列化 XML

    复制 序列化对象时省略所有 xml 命名空间 不一样 我想以另一种方式 反序列化 我有一个 C 类 如下所示 System CodeDom Compiler GeneratedCodeAttribute xsd 2 0 50727 42 S
  • Angular2在路由器出口之外获取路由器参数

    我有一个仪表板应用程序 它由一个树视图组件 列出了各种内容节点 和一个仪表板编辑组件组成 该组件根据选择的树分支呈现一些可编辑的内容 例如树是这样的 Football Premier League Arsenal Chelsea etc C
  • 如何通过单击可执行 r 文件从 rmd 脚本编织 pdf?

    Synopsis 我想通过单击文件 图标从 rmd 脚本生成 pdf 文件 这样我的同事就不会因为先打开 RStudio 而精疲力竭 问题 当我看到this在 R bloggers 上 并让它工作起来 我认为我正在接近从脚本编写到共享我的工
  • 如何在 Nodejs Express 中提供图像

    我有这个代码 var express require express var http require http var app express var server http createServer app app use expres
  • std::unique_lock 或 std::lock_guard

    我有两个用例 答 我想同步两个线程对队列的访问 B 我想同步两个线程对队列的访问并使用条件变量 因为其中一个线程将等待另一个线程将内容存储到队列中 对于用例 A 我看到代码示例使用std lock guard lt gt 对于用例 B 我看
  • 在 Javascript/Jquery 中将 URL 图像转换为 Base64 或 Blob 的简单方法

    我正在为一个简单的应用程序开发离线模式 并且我正在使用 Indexeddb PounchDB 作为库 我需要将图像转换为 Base64 或 BLOB 才能保存它 我已经尝试过这段代码 它仅适用于一张图像 提供的图像 我不知道为什么它不适用于
  • Windows/C++:如何使用未注册的 COM dll

    在我们的应用程序中 我们需要使用一个之前未在系统中注册的COM dll 即msdia100 dll 早些时候 我们刚刚通过以下代码调用其 DllRegisterServer 来调用 DLL Register DIA DLL required
  • 使用 Jersey 的 Java 异步 REST Web 服务?

    我需要实现一个 Java REST Web 服务 我们使用 Jersey 框架 它基本上可以 A 在返回响应之前阻止等待某个事件 或轮询事件 b 提供某种 aysnc 行为来通知客户端请求已被处理 我正在考虑返回一个 transaction
  • MVVM:在 ViewModel 之间共享数据

    如何在多个 ViewModel 之间共享数据 例如 application 中有一个名为 Project 的类 public class Project ModelBase private string projectName public
  • 有没有办法通过protractor cli传递多个浏览器

    只是想知道是否可以像这样指定 cli args 到量角器 multiCapability 0 browserName chrome multiCapability 1 browserName firefox 以便它覆盖量角器conf文件中定
  • 获取最后一个顶级命令作为字符串

    有没有办法将最后一个顶级命令存储到字符串中 而无需将历史记录保存到文件中并将其读回以获取最后一个命令 我有这方面的代码 lastcmd lt function tmp lt tempfile savehistory tmp If we ca
  • XPath:一起选择自己和跟随的兄弟姐妹

    div dt Test 1 dt dd dd dt Test 2 dt dd dd div 到目前为止我已经写了这个 XPath dt contains text Test self dt following sibling dd 但这并没
  • Node.js - 设置系统日期/时间

    有没有办法从 Node js 服务器设置操作系统上的日期 时间 有很多关于如何更改时区的示例 但我需要更改电脑的实际日期 时间 我的回答基于 Mimouni的回答https stackoverflow com a 23156354 1799
  • Caliburn.Micro 嵌套 ViewModel 最佳实践

    这是一个很长的问题 所以请耐心等待 目前 我正在开发一个小工具 旨在帮助我跟踪故事中的无数角色 该工具执行以下操作 加载当前以 json 形式存储在磁盘上的字符并将它们存储在列表中 该列表通过 ListBox 在 Shell 中呈现 如果用