4

What's the bare minimum to get a View and its associated ViewModel work together?

How can ReactiveUI handle when a View is composed of subviews (that can relate to its parent with a cardinality of 1x1 or 1xN)? Like a View with 2 SubViews and each SubView having lists of ViewModels.

SuperJMN
  • 13,110
  • 16
  • 86
  • 185

1 Answers1

6

According to the docs:

The easiest way to use View Location is via the ViewModelViewHost control, which is a View (on Cocoa, a UIView/NSView, and on XAML-based platforms a Control) which has a single ViewModel property. When the ViewModel property is set, View Location looks up the associated View and loads it into the container.

That's what I usually do. All your Viewmodels/SubViewmodels etc. just have a corresponding View that implements IViewFor<whateverViewModel>. I use WPF, and this just means I have to plop in one boiler-plate ViewModel dependency property and it is good to go. Then you register with ReactiveUI's IoC container, Splat:

To use View Location, you must first register types, via Splat's Service Location feature.

 Locator.CurrentMutable.Register(() => new ToasterView(), typeof(IViewFor<ToasterViewModel>));

So basically whenever your View hosts a/many ViewModelViewHost control(s) on it, once you set or bind a viewmodel to it, it will look up and load the registered view. ViewModelViewHost is a container control that hosts a View.

The Views are aware of the ViewModels, but the ViewModels are not aware of the Views.

As far as hierarchy goes, ViewModelViewHost will update based on whatever ViewModel is bound to it, and they will turtle all the way down. Usually my top-level Views are almost all a bunch of ViewModelViewHost controls and they just drill-down from there. Using ReactiveUI's .WhenAny() methods, you can watch properties up and down the ViewModels/SubViewModels etc. hierarchy easily and without having to worry about resubscription or null-checks.

Jon Comtois
  • 1,824
  • 1
  • 22
  • 29
  • This is almost perfect! The only thing is, the registration type is wrong - should be: `Locator.CurrentMutable.Register(() => new ToasterView(), typeof(IViewFor));` – Ana Betts Nov 13 '14 at 19:14
  • 1
    Fixed it, SO block quote formatted the cut and paste incorrectly confusing me. – Jon Comtois Nov 13 '14 at 19:57
  • I'm trying to set up everything to make a MainWindows and a MainViewModel work together, but no dice. Do I need to create a bootstrapper? if I instantiate MainViewModel, is the view instantiated and injected automatically? Do I need a DP in each view for the ViewModel property in the IViewFor? – SuperJMN Nov 18 '14 at 21:37
  • Also, one of my main concerns is that that you have to hard-couple the View to the ViewModel. – SuperJMN Nov 18 '14 at 21:58
  • @SuperJMN I used to be concerned with "hard-coupling" the View and the ViewModel, then I figured it wasn't a big deal. If it is a real concern, one recommendation would be to just use an interface in between the View and ViewModel. I.E. View implements IViewFor and ViewModel implements IViewModel. Then you can still keep the View non-bound to the implementation. – Jon Comtois Nov 19 '14 at 01:29
  • @SuperJMN No, you don't need a bootstrapper. For WPF set up your Splat registrations in App.xaml.cs. Make your MainWindow contain a ViewModelViewHost control and have its ViewModel property bind to the datacontext, or add a ViewModel DP and bind the control ViewModel to that. Either have App instantiate window automatically, or override App.OnStartup and do it yourself. ViewModelViewHost will instantiate for you if registered. I believe you need DP for each ViewModel property on each IViewFor view to make it work right. – Jon Comtois Nov 19 '14 at 01:35
  • I've made my first successful attempts and they went OK, but I have another problem: how can I inject the MainViewModel to the MainWindow with DI? for child views it's OK with the VMVH, but I cannot do it with the initial case: XAML instantiates MainWindow and its VM isn't injected automatically :( – SuperJMN Nov 22 '14 at 15:55
  • 1
    Don't use the App.xaml to auto create the window. Use DI container to create it and inject view model. Override the App.OnStartup method, get your DI container and resolve your window. Then set MainWindow to that instance and show it. – Jon Comtois Nov 22 '14 at 23:13
  • How should I do it? I currently have this: Locator.CurrentMutable.Register(() => new MainViewModel(), typeof(IViewFor)); If I resolve with Locator.Current.GetService(), MainWindow is not automatically created. Can you, please, paste some sample. Thanks! – SuperJMN Nov 23 '14 at 01:24
  • 1
    Not enough room in comment for sample. You are almost there. Use DI container OR 'new' keyword to create Window in OnStartup method. Nothing will make it automatically be created for you if you took it out of the App.xaml, which you should have. Set that Window's DataContext (or Viewmodel property if it has IViewFor) to the you also got VM from the DI container. App.MainWindow = that Window. App.MainWindow.Show(). That's it, really. – Jon Comtois Nov 23 '14 at 05:35
  • Oh sweet, all I have to do is use this little locator command to bind my....OOOPS! It doesn't work with masterdetail view. I've never seen anything so cool that I desperately want to use but cant because every time I turn around there is a roadblock! – Post Impatica Nov 14 '17 at 20:00