Caliburn.Micro nested methods ViewModels

This is a pretty long question, so please bear with me.

I'm currently developing a small tool to help me track a lot of characters in my Stories.

The tool performs the following actions:

  • Download the characters that are currently stored as json on disk and save them in a list that is presented in the shell through the ListBox.
  • If the user opens the character, the shell, which is Conductor<Screen>.Collection.OneActive , opens a new CharacterViewModel , which comes from Screen .
  • Character receives a character to be opened through the IEventAggregator message IEventAggregator .
  • In addition, CharacterViewModel has various properties, which are SubModels that are associated with various routines.

And here is my problem: I am currently initializing sub ViewModels manually when the ChracterViewModel initialized. But that sounds suspicious to me, and I'm sure there is a better way to do this, but I don’t see how I should do it.

Here is the CharacterViewModel 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; } } 

For Completion Sake, I also give you one of the ViewModels submenus. The rest do not matter, because they are structured in the same way, they simply 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 Screens, Conductors and Compositions , IResult and Coroutines and removed the rest of the Documentation, but for some reason I cannot find what I am looking for.

// edit: I have to mention the code that works for me, fine. I am simply not satisfied with this, since I think that I do not quite understand the concept of MVVM and therefore I am making faulty code.

+6
source share
1 answer

There is nothing wrong with having one ViewModel create multiple child ViewModels. If you are building a larger or more complex application, it is almost inevitable if you want your code to be read and maintained.

In your example, you instantiate all four child ViewModels whenever you instantiate a CharacterViewModel . Each of the child ViewModels takes an IEventAggregator as a dependency. I would suggest that you consider these 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); } 

This way you can make setters for child properties of ViewModel private.

Change the child ViewModels to import IEventAggregator through the constructor installation:

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

In your example, two of these child ViewModels are passed by an instance of Character data in their constructors, implying a dependency. In these cases, I would give each ViewModel child the public Initialize() method, where you set the Character data and activate the event aggregator subscription:

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

Then call this method in the 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(); } 

For child ViewModels, where you only update Character data through EventAggregator , leave a call to this.eventAggregator.Subscribe(this) in the constructor.

If any of your child ViewModels are not really required for the page to work, you can initialize these VM properties by importing the properties:

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

Properties are not imported until the constructor completes.

I would also suggest handling the instantiation of ICharacterSaveService through constructor injection, rather than explicitly creating a new instance each time you save data.

The main goal of MVVM was to allow interface designers to work with the user interface layout in a visual tool (Expression Blend) and encoders to implement behavior and business without interfering with each other. The ViewModel provides data for binding to a view, describes the behavior of a view at an abstract level, and often acts as an intermediary for internal services.

There is no β€œright” way to do this, and there are situations where this is not the best solution. There are times when the best solution is to throw an extra layer of abstraction using the ViewModel and just write the code. Therefore, although this is a great structure for your application as a whole, do not fall into the trap of forcing everyone to fit into the MVVM pattern. If you have some more graphically complex user controls where it just works better with some code, then what should you do.

+7
source

Source: https://habr.com/ru/post/974763/


All Articles