4
votes

I've been working on an MVVM application in C# but consistiently run into some problems when working with the collections of ViewModels my View digests. Specifically, they all tend to relate to the issue of the Model being a private member of the ViewModel.

An example of this is creating new ViewModels (as requested by the View). For some preamble (although you might not need these to help me) here are example Model and ViewModel classes:

Private Class Model()
{
    public string Name { get; set; }
}

Public Class ViewModel()
{
    Private Model _Model;

    Public Void ViewModel(Model model)
    {
        _Model = model;
    }

    Public String Name
    { 
        get
        {
            return _Model.Name;
        }
        set
        {
            _Model.Name = value;
        }
    }
}

The entire model is never directly exposed as a public member of the ViewModel. The MainWindowViewModel handles collections of Models (private, the view cant see these) and ViewModels (public for View digestion):

Public Class MainWindowViewModel
{
    Private List<Model> _NamesModel;
    Private ObservableCollection<ViewModel> _NamesViewModel;

    Public Void MainWindowViewModel()
    {
        //Lets pretend we have a service that returns a list of models
        _NamesModel = Service.Request();
        foreach(Model model in _NamesModel)
        {
            ViewModel viewmodel = new ViewModel(model);
            _NamesViewModel.Add(viewmodel);
        }
    }

    Public ObservableCollection<ViewModel> NamesViewModel
    {
        get
        {
            return _NamesViewModel;
        }
    }
}

Now thats the preamble but now I have a problem. How do I add a new ViewModel? Do methods within my view create a new ViewModel and populate that? Being a purist, I'm assuming the View should not be allowed to create or populate Models at all. Should my ViewModel contain a constructor that accepts nothing (i.e. no underlying model) and instead creates a blank to populate?

These kinds of issues keep coming up with a "pure" MVVM approach. I've had to create a public method in my ViewModel (bool compare(Model model)) that will compare a model (ready for deletion etc.) to it's internal one. If the models were publicly exposed (breaking purity) then it would be much easier to do stuff like find the ViewModel thats connected to a Model.

2

2 Answers

1
votes

All the creation-related issues can be resolved with introduction of factory design pattern. The factory will take care of creating view models basing on model that was provided.

public class MainWindowViewModel
{
    private List<Model> _NamesModel;
    private ObservableCollection<ViewModel> _NamesViewModel;
            private IViewModelFactory factory;

    public void MainWindowViewModel(IViewModelFactory factory)
    {
        //Lets pretend we have a service that returns a list of models
        _NamesModel = Service.Request();
        _NamesViewModel = factory.CreateNamesViewModels(_NamesModel);
    }

    public ObservableCollection<ViewModel> NamesViewModel
    {
        get
        {
            return _NamesViewModel;
        }
    }
}

What is more, you could even get rid of Service dependency in view model and move it to the factory itself, thus reducing the need to keep model in view model (admittedly though, removal of model might not work in more complex scenarios):

public ObservableCollection<ViewModel> CreateNamesViewModels()
{
    var models = Service.Request();
    return new ObservableCollection(models.Select(m => new ViewModel(m)));
}

Also, your main window view model can expose commands that utilize factory to create any new instances. This way, no model is leaking to view and also no creation details are exposed (since commands will hide actual implementation).

2
votes

I can sympathize with some of those problems. I recently wrote an MVVM application where similar questions came up frequently. One of the tricks is to decide - definitively - which class is going to be responsible for Model instances. Do you want it to be your MainWindowViewModel? Or your NameViewModel? You don't want to share the responsibilities of creating/deleting the model between both of those classes; you'll have quite a logistical nightmare.

Secondly, even a "pure" MVVM approach doesn't dictate that you can't expose the model publicly. You said yourself that doing so would save you a lot of headache: DO IT. MVVM dictates only that the ViewModel has no knowledge/access of the View. There are many "official" MVVM examples that go so far as to implement their Model using the INotifyPropertyChanged interface, and bind directly to properties on the Model.

Personally, I think I would dictate control of the NameModel to the NameViewModel. This means that you should remove the list of NameModels completely from the MainWindowViewModel. If you want to give the NameViewModel an optional constructor which takes a Model, that would be fine too.

I'm a fan of this approach:

public NameViewModel : ViewModelBase
{
    public NameModel Model
    { 
        get { /* get stuff */ }
        set { /* set stuff */ }
    } 

    // Default constructor creates its own new NameModel
    public NameViewModel()
    {
        this.Model = new NameModel();
    }

    // Constructor has a specific model dictated to it
    public NameViewModel(NameModel model)
    {
        this.Model = model;
    }

    //Model wrapper properties
    public String Name
    { 
        get { return Model.Name; }
        set { Model.Name = value; }
    }
}

and...

public class MainWindowViewModel
{
    Private ObservableCollection<ViewModel> _NameViewModels;

    Public Void MainWindowViewModel()
    {
        //Lets pretend we have a service that returns a list of models
        var nameModels = Service.Request();
        foreach(Model model in nameModels)
        {
            ViewModel viewmodel = new NameViewModel(model);
            NameViewModel.Add(viewmodel);
        }
    }

    Public ObservableCollection<ViewModel> NameViewModels
    {
        get
        {
            return _NameViewModels;
        }
    }
}

In this way your MainWindowViewModeldoesn't keep an entirely separate copy of the Models; it only tracks the NameViewModels. Each NameViewModel is responsible for its own underlying model, while still making the option available to have a specific model passed to it during construction.