WPF DataTemplate / DataTemplateSelector -- Best approach for a ViewModel used by 2 different Views?

myermian picture myermian · Apr 14, 2011 · Viewed 7.2k times · Source

Basically, I have the following scenario:

ViewModel: FooViewModel : BaseViewModel, BarViewModel : BaseViewModel
Views: MainView, FooView, BarView

Right now I "inject" the view and set the DataContext using DataTemplate and DataTemplateSelector. Obviously, my ItemsControl ItemSource is bound to an ObservableCollection<BaseViewModel> in which it contains (for now) an instance of a FooViewModel and a BarViewModel

The problem is I want to introduce a AlternateFooView which I want to utilize the same FooViewModel. I figure I'll create another DataTemplate and have my DataTemplateSelector return it, but there needs to be special logic to determine which DataTemplate to return (I can't just go by which ViewModel is there), and that means I'll have to have some type of property or field in the BaseViewModel. I don't know if that's really a good idea because that seems to be introducing a field/property into the ViewModel that is only used to select a view. It won't hurt my unit testing, but it seems like a waste to include a field just to help decide which UI View to choose. I don't think it breaks MVVM, but I'm curious if anyone out there has any other better ideas? The alternative ideas I had I dislike even more...

Idea #2:
- Turn FooViewModel into a base class that 2 different FooViewModel's extend (i.e. BaseFooViewModel, FooViewModel, DifferentFooViewModel). This seems stupid because there really isn't any difference between FooViewModel and DifferentFooViewModel aside from their class type.

Idea #3:
- Just copy FooViewModel and make it FooViewModel2 (it'll be exactly identical to FooViewModel). This seems even worse than idea #2.


Sample-Code (Adjusted obviously):

public abstract class BaseViewModel : NotificationObject
{
    //Common Stuff
}

public abstract MainViewModel : NotificationObject
{
    public MainViewModel()
    {
        MyItems = new ObservableCollection<BaseViewModel>()
        {
            new FooViewModel();
            new BarViewModel();
            new FooViewModel(); //New Item -- I want it to use the DifferentFooView
        }
        //Load items from a DAL later
    }

    public ObservableCollection<BaseViewModel> MyItems { get; set; }

    //Other Stuff
}

<l:MyItemsControl ItemSource={Binding MyItems} ContentTemplateSelector={StaticResource MyTemplateSelector} />


Thanks!

Answer

Josh Smith picture Josh Smith · Apr 14, 2011

I agree with krishnaaditya that the question really boils down to what determines which View to use based on the state of the ViewModel. This type of logic is often placed into a template selector, which works great. If you don't want to put that logic into the template selector, consider externalizing it by using my Routed Template Selection approach. That makes it easy to delegate the template selection logic by using a routed event.

The idea you proposed in your comment about using a DataTrigger could also work, but that reintroduces the need for a property on the VM object that indicates which View to load (which you said you don't want). In my opinion, that's not necessarily a bad thing.