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

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

该工具执行以下操作:

  • 将当前存储为json的字符加载到磁盘上,并将它们存储在列表中,该列表通过列表框在Shell中显示。
  • 如果用户然后打开一个字符,则 shell 程序(即Conductor<Screen>.Collection.OneActive)将打开一个新的CharacterViewModel,该新的Screen是从Character派生的。
  • IEventAggregator获取将通过CharacterViewModel消息系统打开的角色。
  • ChracterViewModel还具有各种属性,这些属性是绑定(bind)到各种 subview 的子ViewModel。

  • 这是我的问题:
    目前,在初始化CharacterViewModel时,我会手动初始化子ViewModels。但这对我来说听起来像是可疑的,我很确定有更好的方法可以做到这一点,但是我不知道应该怎么做。

    这是ojit_code的代码:
    /// <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;
        }
    }
    

    对于清酒,我还为您提供了一个子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);
            }
        }
    }
    

    我已经读过Screens, Conductors and CompositionIResult and Coroutines并浏览了本文档的其余部分,但是不知何故找不到所需的内容。

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

    最佳答案

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

    在您的示例中,每当创建CharacterViewModel实例时,您都将实例化所有四个子ViewModel。每个子ViewModels都将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属性上的 setter 设为私有(private)。

    更改您的 child ViewModels以通过构造函数注入(inject)导入IEventAggregator:
    [ImportingConstructor]
    public CharacterGeneralViewModel(IEventAggregator eventAggregator)
    {
        this.eventAggregator = eventAggregator;
    }
    

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

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

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

    如果页面正常运行实际上不需要您的任何子ViewModel,则可以通过属性导入来初始化这些VM属性:
    [Import]
    public CharacterGeneralViewModel CharacterGeneralViewModel { get; set; }
    

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

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

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

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

    10-05 23:59