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.