canton7 / Stylet

A very lightweight but powerful ViewModel-First MVVM framework for WPF for .NET Framework and .NET Core, inspired by Caliburn.Micro.
MIT License
990 stars 143 forks source link

Trying to understand IChild and how to set the Parent property... #108

Closed WillBellJr closed 4 years ago

WillBellJr commented 4 years ago

Hi, I have a MainWindowViewModel : Screen, and a UpperToolbarViewModel : Screen user control area docked at the top of my MainWindowView.

I'm not understanding, from the documentation; it claims that any View derived from an IChild will have its Parent set?

Everything I've tried, short of just manually setting the Parent member to "this" when I construct the UpperToolbarViewModel leaves the Parent member Null?

I saw one of the answers where it was said to use SomeViewModel.ConductWith(this); when managing multiple active Views within the main window.

Being that I'm passing in the MainWindowViewModel for the this parameter, I was sure the Parent member of the Child view would be set, but debugging though the child view constructor shows the Parent member is still null?

Am I supposed to "manually" set the Parent member for each of my Screen based ViewModels, or is the Parent member supposed to automatically get set as per what's mentioned in the documentation?

I'm thinking I shouldn't have to pass a reference to my MainWindowViewModel to all the child ViewModels since its stated that anything that Implements IChild will automatically get the Parent member set?

Any examples, or pointers as to what I may be doing wrong getting the framework to set the Parent of my child ViewModels is appreciated!

Thanks!

canton7 commented 4 years ago

It's used by conductors, see here. The parent/child relationship is mainly used for two things: managing the child's lifecycle, i.e. activating/deactivating it when appropriate (which can also be done with ConductWith, if you just want the child's activation state to mirror the parent's), and allowing the child to request that it's closed (which only really makes sense in the conductor context, where another child will probably be chosen to take its place).

For your UpperToolbarViewModel (which is presumably shown all of the time), I wouldn't worry about the parent/child relationship. If you want this VM to be activated/deactivated along with its parent, then use ConductWith.

WillBellJr commented 4 years ago

Hi, thanks for your quick response - I really appreciate you for sharing your wonderful MVVM framework with the community, compared to DevExpress MVVM that I'm coming from, yours is so much easier to use and lightweight!

Yes by now, I've poured over your wiki documentation for months - especially the Conductors page!

Perhaps I'm just misunderstanding how the Parent property is set or used. I can appreciate that it's used for lifetime management, activation, etc., I have yet to use that functionality being that so far I'm using persistent Toolbars.

All I was attempting to do was get a reference back to my MainWindowViewModel since that VM exposes services that the UpperToolbar may be interested in using. (I have broken out a lot of those services now using the IOC w/constructor injection, but it was just a nagging question in my mind as to why the Parent property is Null when I test it in the ToolBar's constructor.

MainWindowViewModel: ` public MainWindowViewModel(IWindowManager windowManager,IUserEnvironmentInfo userEnvironmentInfo, IApplicationInfo appInformation, IApplicationEventServices appEventServices) {

  UserEnvironmentInfo      = userEnvironmentInfo;
  ApplicationEventServices = appEventServices;
  ApplicationInfo          = appInformation;
  WindowManager            = windowManager;

  UpperToolbar = new UpperToolbarViewModel(UserEnvironmentInfo,ApplicationEventServices);
  UpperToolbar.ConductWith((IScreenState) this); // Does this set the Parent property??

}`

UpperToolbarViewModel: ` public UpperToolbarViewModel(IUserEnvironmentInfo userInfo, IApplicationEventServices appEvents) { AppEvents = appEvents; UserInfo = userInfo;

  Text = "Hello World!";

  var parent = this.Parent; // <--- this.Parent == null??

}

`

Previously, I was just passing in as a parameter a reference to the main window VM, but from my understanding of the Docs, I thought the UpperToolbar Parent property would be already set when constructed by the MainWindow view model.

Perhaps I'm simply wrong testing Parent within the constructor, with the Parent property being set "later", in its life cycle, e.g. when the VM is Activated etc?

Edit: Noap - Parent is still null in the OnActivate() override...

...I will add, this desire of being able to "reach back into" the MainWindowViewModel, is due to me trying to get a reference to the IWindowManager property that my MainWindow VM is holding.

I've tried adding an IWindowManager parameter to my Toolbar VMs but, the IoC isn't injecting it into the constructor like it does for the MainWindowVM (I'm assuming because the BootStrapper is tied to the MainWindow VM at startup).

Previously I was simply passing a IWindowManager reference as a constructor parameter for the child VMs, but it felt like I'm missing something, on how to call a dialog from my child view model(s).

That is why I was looking to use the Parent reference - to get my hands on the IWindowManager that my MainWindowVM is holding.

Is there a proper way to open a dialog, (or get a handle to IWindowManager) from conductor managed, child view models?

Thanks!

canton7 commented 4 years ago

Perhaps I'm just misunderstanding how the Parent property is set or used.

Parent is only used when the child wants to be able to request that the parent closes it. IChildDelegate, the interface implemented by objects assigned to the Parent property, has a single method which is CloseItem.

All I was attempting to do was get a reference back to my MainWindowViewModel since that VM exposes services that the UpperToolbar may be interested in using. (I have broken out a lot of those services now using the IOC w/constructor injection, but it was just a nagging question in my mind as to why the Parent property is Null when I test it in the ToolBar's constructor.

You're welcome to use the Parent property in this way if you wish, but in that case you'll have to set it yourself. The only place where Stylet sets it is when the child is displayed by a conductor.

The ideal situation here is inject those services into the child, so it doesn't have a dependency on its parent.

ConductWith doesn't set Parent. It just activates/deactivates/closes some VM when another VM is activated/deactivated/closed.

WillBellJr commented 4 years ago

Thank you for the explanation and advice.

I've consolidated all of my MainWindowViewModel resource properties into a singular interface (IMainWindowViewModelResources - which the MainWindow VM implements), and I pass that interface to my child views; this gives them access to anything they could possibly need (events, window manager etc.,) - eliminating the need for multiple injections into those views.

I can live with this until further learning and/or experience points me down a different path.