Sub-ViewModels

Sep 23, 2009 at 6:24 PM
Edited Sep 23, 2009 at 6:38 PM

I realize Onyx is still in it's early stages and the author is working very hard on Specificity, but I'm curious about the recommended approach for something and I'm wondering if he or anyone could point me in the right direction. 

Basically, I have a view/viewmodel pair represents the display of a list of items.  I understand how to associate the top-level view with the viewmodel, but I'm not sure, given the view-first approach that Onyx takes (at least by default, I hope), how to get the item views associated with the item viewmodels.  I think the problem here is that you can't take a view-first approach to a variable list of items, so I'm thinking what needs to happen is that the viewmodel must be created and the view is associated with after the fact.  Or maybe the idea is that the view for all the items is the same, meaning that they share a reference to the top-level view.  This doesn't seem right, or as ideal as the viewmodel-first approaches (as advicated by Josh Smith and others) in this situation.  It makes me think that I should be able to pass an instance of the pre-instantiated viewmodel in the Model attached property on the View object, but it doesn't look like that's what it's set up to do.  Or maybe I'm reaching by trying to make the items viewmodels in the first place?

Anybody know what the best way to deal with this is?

BTW, I really like what I'm seeing in Onyx.  As someone who's written his own Mvvm frameworks on more than one occasion, I feel humbled when I look at the code.  I also really appreacitate that Onyx (unlike some other "frameworks" I'm seeing out there) at least attempts to tackle the harder aspects of Mvvm like dialogs and focus.  I really hope the author continues on with it as I think its got the potential to be what we've all been waiting for.

Thanks to anyone who can help.

Coordinator
Sep 23, 2009 at 9:57 PM

Onyx need not take a View first approach. ViewModel first would work with Onyx just as well, though the framework doesn't provide you with any mechanisms out of the box for this. That's something that I've been spiking, and have a working sample for.

In your case, the key here is that View.Model can take a view model instance instead of a Type. This means, for instance, you could use a classic DataTemplate approach for your items, and assign the View.Model="{Binding}". The problem here, at least with the current codebase, is that while this associates the view with the view model, it doesn't associate the view model with the view. You'll have to modify the OnModelChanged handler to do this for you somehow. Again, this is something that will be addressed as soon as I can get back to working on this project.

Oct 12, 2009 at 5:22 PM

Thanks for the advice.  Having now had a chance to implement and test out your suggested approach I can tell you it works out pretty well.  I added a little code to the OnModelChanged handler to set the view property of the viewmodel if it had hadn't been set previously. 

 

            //this would be if the viewmodel constructed first  
            ViewModel viewModel = model as ViewModel;

            if (viewModel != null && viewModel.View == null)
                viewModel.View = GetView(source);

I modified the ViewModel to have a default constructor and changed the original constructor that tool the view as a parameter to set the view property. 

protected ViewModel() { }

protected ViewModel(View view) 
     : this()
{
     this.View = view;
}

Then inside the view property setter i call out to a virtual method where all of the stuff to wire up the view happens.

public View View
        {
            get { return this.view; }
            set
            {
                if (this.view != null)
                    throw new InvalidOperationException("View cannot be set more than once.");

                this.view = value;

                this.OnViewAttached();
            }
        }

        protected virtual void OnViewAttached()
        {
            IElementLifetime lifetime = this.View.GetService<IElementLifetime>();
            
            if (lifetime != null)
            {
                lifetime.Initialized += (s, e) => this.OnViewInitialized();
                lifetime.Unloaded += (s, e) => this.OnViewUnloaded();
                lifetime.Loaded += (s, e) => this.OnViewLoaded();
            }

            IWindowLifetime windowLifetime = this.View.GetService<IWindowLifetime>();

            if (windowLifetime != null)
            {
                windowLifetime.Closing += (s, e) => this.OnViewClosing(e);
                windowLifetime.Closed += (s, e) => this.OnViewClosed();
            }

            ICommandTarget commandTarget = this.View.GetService<ICommandTarget>();
            
            this.commandBindings = commandTarget == null ? 
                new CommandBindingCollection() : commandTarget.CommandBindings;
        }

I know I'm probably bastardizing your code somewhat, but I'm calling this a stop gap till you come back and bake in the more elegent solution.

Thanks again for putting up the framework in the first place.  As someone who continues to search for the most ideal setup for implementing mvvm in WPF, it remains my favorite.  n in its unfinished state ; )