views:

307

answers:

4

My Model is a generic class that contains a (for example) Value property which can be int, float, string, bool, etc. So naturally this class is represented something like Model<T>. For the sake of collections Model<T> implements the interface IModel, although IModel is itself empty of any content.

My ViewModel contains and instance of Model<T> and it is passed in through ViewModel's constructor. I still want to know what T is in ViewModel, so when I expose Model to the View I know the datatype of Model's buried Value property. The class for ViewModel ends up looking like the following:

class ViewModel<T>
{
   private Model<T> _model;

   public ViewModel(Model<T> model) { ....blah.... }

   public T ModelsValue {get; set; }

}

This works fine, but is limited. So now I need to expose a collection of IModels with varying Ts to my View, so I'm trying to set up an ObservableCollection of new ViewModel<T>s to a changing list of IModels. The problem is, I can't figure out how to get T from Model<T> from IModel to construct ViewModel<T>(Model<T>) at runtime.

In the VS2010 debugger I can mouseover any IModel object and see its full Model<int> for example at runtime so I know the data is in there.

Any ideas?

A: 

As a side note, when you say "when I expose Model to the View, you aren't following MVVM conventions, for whatever that's worth. In MVVM, the model itself should never be exposed to the view.

That being said, you can expose the type of T in this way.

pubic Type ModelType
{
    get { return typeof(T); }
}

If that suits your purposes.

Adam Robinson
This works to get the type, but I cannot call ModelView<model.ModelType>(model); that won't compile.
bufferz
Also, about your side note, I don't expose my model -- I wrote it that way for code simplicity.
bufferz
*"In MVVM, the model itself should never be exposed to the view."* - otherwise, the MVVM police will take away your family, as [Laurent Bugnion](http://www.galasoft.ch/) would say (ironically)... Of course, you can expose the model to the view, it is your personal decision. MVVM is just a pattern, and patterns are ideas, and ideas can be modified. Anyway, I do not want to start a decision about that here because it would be off-topic.
gehho
@gehho: Of course you can expose your model to the view. The point of my statement is that *doing so means you aren't using MVVM*. If you didn't want to start a discussion, why bring it up?
Adam Robinson
@Adam: No offense intended, really, but I just do not like it if people claim that MVVM does not allow to 1) have a Model property in the VM, 2) have a reference to the View in the VM, 3) whatever else - just because *it confuses people*. I am not aware of an ISO standard for MVVM defining all the details. Furthermore, *a lot of people* use a Model property, e.g. Karl Shifflett obviously does so, as you can see in the graphic in [this blog post](http://karlshifflett.wordpress.com/mvvm/wpf-line-of-business-introduction/). End note: MVVM is a concept, but not a law.
gehho
@gehho: No offense taken, but it seems rather silly to say "I don't want to start a discussion" after hijacking someone else's answer. It essentially says "I want to say my piece, but I don't want anyone to respond to it." Nobody says that you cannot expose the model, *just that the design philosophy behind MVVM says that you shouldn't.*. It's the entire *point* of MVVM to hide the model from the view, and *make the view's model the **viewmodel**.* End note: if you don't want to start a discussion, don't comment.
Adam Robinson
*"I want to say my piece, but I don't want anyone to respond to it."* Actually, that was about my intention, yes, just without that negative connotation. The reason for my comment was to let an unexperienced reader know that - in certain situations - it might be absolutely fine to have a Model property in the VM. Now, I am perfectly fine. :)
gehho
@gehho: It's absolutely fine to do so, it just isn't following MVVM conventions. Hence my pointing that out.
Adam Robinson
A: 

The alternative would be to have an interface IModelValue that would expose T from Model<T>. Then your ViewModel class would look like:

class ViewModel
{
   private IModel _model;

   public ViewModel(IModel model) { ....blah.... }

   public IModelValue ModelsValue {get; set; }
}
PL
A: 

C# generics won't allow generic type as type parameter:

ObservableCollection<ViewModel<T>>

Above is not only illegal in C#, but also makes no sense because it would break static type constraints.

I'm guessing what you really are trying to do is:

class ViewModel<T> : IMyViewModel {...}

new ObservableCollection<IMyViewModel>()

than you need some kind of factory that would produce IMyViewModel instances based on IModel runtime type:

public IMyViewModel CreateMyViewModel( IModel model){
    if (model is Model<A>)
        return new ViewModel(model as Model<A>);
    if (model is Model<B>)
        return new ViewModel(model as Model<B>);
    ...etc..
}

thus, having a

IEnumarable<IModel> models = ...

you can get

var myVMs = 
    from m in models select CreateMyViewModel(m);

myCollection = new ObservableCollection<IMyViewModel>(myVMs);
majocha
If the object in `myCollection` implements `INotifyCollectionChanged` (as does `ObservableCollection<T>`), it implies that the collection sends notifications when the source data changes. In the case you gave, it doesn't - misuse of this type leaves the developer with no way to tell whether the UI should use polling or events to update the view.
280Z28
Could you please elaborate, why it doesn't send notifications? Where is the misuse?
majocha
+3  A: 

Here's what I'm using for view model collections:

Preface:

Your view model objects can be weakly typed. Give IModel a property object Value {get;} and expose that in a ModelViewModel : ViewModel<IModel> that you use for all IModel objects (see my ViewModel<T> implementation below). If you have various combinations of ObservableCollection<IModel>, ICollection<Model<T>>, etc., the framework shown here is a lifesaver. If you still need generic view model, you can derive a ModelViewModel<T> : ModelViewModel that takes a Model<T> in its constructor. The logic to create the appropriate type would go in the converter passed to ViewModelCollection.Create below. Do be warned that this design will impose a performance penalty.

ModelViewModel CreateModelViewModel(IModel model)
{
    Type viewModelType = typeof(ModelViewModel<>).MakeGenericType(model.Type);
    ModelViewModel viewModel = Activator.CreateInstance(viewModelType, model);
    return viewModel;
}

Example usage:

public class CatalogViewModel : ViewModel<ICatalog>
{
    public CatalogViewModel(ICatalog catalog)
        : base(catalog)
    {
        Func<ICatalogProduct, ProductViewModel> viewModelFactory = CreateProductViewModel;

        this.Products = ViewModelCollection.Create(catalog.Products, viewModelFactory);
    }

    public ICollection<ProductViewModel> Products
    {
        get;
        private set;
    }

    private ProductViewModel CreateProductViewModel(ICatalogProduct product)
    {
        return new ProductViewModel(product, this);
    }
}

Benefits:

  • Uses lazy implementations to allow for efficient and even recursive bindings in trees.
  • The view model collections only implement INotifyCollectionChanged if the underlying model collection implements INotifyCollectionChanged.

Overview of the classes (full implementations linked to github):

  • ViewModel<TModel>: Base class for my view model classes. Exposes a Model property that I use in the view model's backing code.

  • ObservableViewModelCollection<TViewModel, TModel>: Lazy (actually not currently, but definitely should be), observable mapping from a model to a view model. Implements INotifyCollectionChanged.

  • ViewModelCollection<TViewModel, TModel>: Lazy mapping from a collection of TModel to a collection of TViewModel.

  • ViewModelCollection: Static helper - returns an ICollection<TViewModel>, using ObservableViewModelCollection<TViewModel, TModel> when the source collection implements INotifyCollectionChanged, otherwise using ViewModelCollection<TViewModel, TModel>.

A few extra types that might be useful for your view model collections:

ConcatCollection: Like ViewModelCollection, this includes a static helper to automatically choose an appropriate implementation. The ConcatCollection concatenates collections by binding directly to the source collection(s).

Here is an example of how I used this type to expose a Children property to the view while maintaining my observable collections all the way to back to the original source.

public class ProductViewModel : ViewModel<IProduct>
{
    public ProductViewModel(IProduct product)
        : base(product)
    {
        Func<IProduct, ProductViewModel> productViewModelFactory = CreateProductViewModel;
        Func<IRelease, ReleaseViewModel> releaseViewModelFactory = CreateReleaseViewModel;

        this.Products = ViewModelCollection.Create(product.Products, productViewModelFactory);
        this.Releases = ViewModelCollection.Create(product.Releases, releaseViewModelFactory);
        this.Children = ConcatCollection.Create<object>((ICollection)this.Products, (ICollection)this.Releases);
    }

    public IList<ProductViewModel> Products
    {
        get;
        private set;
    }

    public IList<ReleaseViewModel> Releases
    {
        get;
        private set;
    }

    public IEnumerable<object> Children
    {
        get;
        private set;
    }

    private ProductViewModel CreateProductViewModel(IProduct product)
    {
        return new ProductViewModel(product);
    }

    private ReleaseViewModel CreateReleaseViewModel(IRelease release)
    {
        return new ReleaseViewModel(release);
    }
}
280Z28