Multiple Views with one viewmodel

Mar 29, 2012 at 12:04 PM

Is there a way to bind multiple views to one instance of a viewmodel.

I have two views. Which belongs to one viewmodel. So now i just want only one instance of the viewmodel.

I use the UserControlbehavior. That creats a new instance oft the viewmodel when i display the view.

Coordinator
Mar 29, 2012 at 12:10 PM

Then you have to set the ViewModel as datacontext yourself. Then it will re-use that one.

Mar 29, 2012 at 12:19 PM

Ok !? Is not clear for me how to do that. Can you give me an example, or explain it more precisely.

Coordinator
Mar 29, 2012 at 12:55 PM

Well, by default, a UserControl (and thus the behavior, its the same) transforms the datacontext (normally a model) into a view model. However, if the datacontext already is a view model, it will use that as view model. However, I strongly advise you not to use the same view model on multiple views at the same time (think about model editing at the same time, what to do when view 1 cancels, etc...).

So, I recommend to use separate instances for each view.

Mar 29, 2012 at 2:23 PM

Example

I have two views. A generalview here you see every room of an house. And you have an detailview. Here you see only one chosen room. In the viewmodel there is the info which is the active corner of a room. So there are properties wich are not in the model but in the viewmodel. And every change  in the model and also in the viewmodels has to shown on both view.

 

Mar 30, 2012 at 7:15 AM

What do you think about my example. I think one viewmodel is enough when i will show on both views always the same. Only in another way.

Coordinator
Mar 30, 2012 at 1:15 PM

I think you should only pass the model to the new view model. The model contains all data, and the view model can inject the active model into another view model.

Mar 30, 2012 at 1:50 PM

No i don't think that the model contains all data. There are also data that is only needed in viewmodel.

Like in my example. When i have a Room with a list of corners. The corners is shown in an combobox. So now the SelectedItem is binding to the property ActiveCorner. And this property does not belong to model. This property belongs to viewmodel.

Why do you need a viewmodel, if you only map all viewmodel  propertys to the model propertys.

Another example. I will show a Rectangle on the a view. In the rectangle model there are the data of size. But in my view i will show the rectangle rotated. So the info about the angle belongs to the viewmodel. Thats MVVM :-).

Coordinator
Mar 30, 2012 at 2:30 PM

So, say you have a valid point, how would you see this supported in Catel?

Mar 30, 2012 at 4:11 PM

That was not the point. You gave me an answer: "Then you have to set the ViewModel as datacontext yourself." That was ok.

I just wan't to gave an answer to your statement: So, I recommend to use separate instances for each view. And i wan't to know your opinion. And a discussion occured.

But maybe we find a solution togehter. When you are interessted in.

 

Coordinator
Mar 30, 2012 at 4:53 PM

Yes, I am a bit tired today, maybe that's just it :)

Let me sleep a night and maybe I will be more awake tomorrow.

Apr 2, 2012 at 9:22 AM

Now any ideas ? :-)

Apr 2, 2012 at 2:01 PM

Maybe with UnityContainer oder Servicelocator?

Coordinator
Apr 2, 2012 at 7:03 PM

The latest version of Catel (unofficial, 3.1) will re-use the same instance of a view model when it is set as a datacontext. What you can also do is write your own "ViewModelManager" which hooks up view models to a specific model. For example, the GetViewModelType(object) method of a user control (see this blog post for full details) can return the right view model type based on the model.

Maybe we can also create an overload which returns the actual view model type (so the logic doesn't even has to instantiate it). Then you would get another overload like this:

protected override IViewModel CreateViewModelInstance(object dataContext)
{
    if (dataContext is MyModel) return new MyModelViewModel(dataContext);

    // Here, you could use an IoC container or custom dictionary with view model instances to return the right instance

    // Return null means that the logic will handle it on his own
    return null;
}

What do you think of such an approach?

Apr 3, 2012 at 12:53 PM
Edited Apr 3, 2012 at 12:56 PM
I'm still thinking about.
Where do i have to implement the
"CreateViewModelInstance" Method? In the view?
And the other problem.

Models: Room 1 and Room 2 (1 Model Class / 2 instances )
Views: GeneralView and DetailView ( 2 View Classes / 4 instances  (2 for every Room) )
ViewModels: RoomViewModel ( 1 ViewModel Class / 2 instances (not 4 - one for every View Instance  and no 1 - only one for one Modeltyp  ) )

 

Oh. Very Complex. I hope you understand my example and my problem.
Maybe this explanation is more clear:
I need one instance of a ViewModel for every Instance of a Model.
And not one instance of a ViewModel for every Instance of a View.

 

So with that statment get only one Instance of a Viewmodel for all models with the same Type.
if (dataContext is MyModel) 
Coordinator
Apr 3, 2012 at 2:06 PM

Yes, it would be implemented in the view. Since you have the datacontext, you can do more than just checking the type. You can make it as crazy as you want, for example:

private static readonly Dictionary<int, IViewModel> _modelViewModels = new Dictionary<int, IViewModel>();

protected override IViewModel GetViewModelInstance(object dataContext)
{
    if (dataContext == null)
    {
        // Let catel handle this one
        return null;
    }

    if (!_modelViewModels.ContainsKey(dataContext.GetHashCode()))
    {
         var vm = new MySuperViewModel(myModel);
         _modelViewModels.Add(dataContext.GetHashCode(), vm);
    }

    // Reuse VM
    return _modelViewModels[dataContext.GetHashCode()];
}

If you put this code in a helper class, you can even share VM instances application wide.

Apr 4, 2012 at 10:02 AM

Where is the GetViewModelInstance defined? I get the error: There is no method for overwrite!

Coordinator
Apr 4, 2012 at 10:09 AM

Not implemented yet, I wanted to discuss it before implementing. If you like it, I will implement it tonight.

Apr 4, 2012 at 10:28 AM

Yes i think thats ok. So you can implement it. Tanks!

Coordinator
Apr 4, 2012 at 7:16 PM

Feature is implemented and committed.

Apr 16, 2012 at 7:03 AM

Thanks. I will try it. Sorry for my late response.

Apr 18, 2012 at 7:21 AM

Hello again. Sorry but i found no Method GetViewModelInstance to override. Where is it?

Coordinator
Apr 18, 2012 at 7:24 AM

In the latest nuget beta package, it should be available. Also, if you compile the source yourself, it should be available.

Apr 18, 2012 at 7:38 AM

I downloaded the 3.1 beta. It's not included. In the nuget package it's the version 3.01. Here it should be included? Little bit strange. What's the difference between the nuget package an the download on this site?

Coordinator
Apr 18, 2012 at 7:42 AM

Strange, when I take a look at the source code for UserControl, it is there. Are you sure your reference have also updated to 3.1? Sometimes visual studio thinks to outsmart you and references the files in the output directory (thus you *think* you have updated the references, but visual studio just changes them).

There should be no different between the nuget packages and the installer. However, we release nuget packages more often (easier, just a build script).

You should use this code in a user control:

protected override IViewModel GetViewModelInstance(object dataContext)
{

} 

Apr 18, 2012 at 8:47 AM

One Moment. I think it is another problem

using Catel.MVVM;
public partial class HouseView : UserControlIViewModelContainer

Here Visual Studio says that Usercontrol is  from  "System.Windows.Controls" and not from "Catel.Windows.Controls"

So its clear why the method GetViewModelInstance is not found.

But what is wrong? My Usercontrol looks like in your examples.

Coordinator
Apr 18, 2012 at 8:49 AM

Either include the Catel.Windows.Controls namespace or make sure to specify Catel.Windows.Controls.UserControl as baseclass.

Apr 18, 2012 at 8:56 AM
Edited Apr 18, 2012 at 8:57 AM

Ok. I just look in your examples. And there is nothing of both. Or i have overlooked something.

Coordinator
Apr 18, 2012 at 8:58 AM

This is probably because the examples always assume Catel.Windows.Controls namespace. Maybe we need to look into that. Does it work for you now?

Apr 18, 2012 at 9:15 AM
Edited Apr 18, 2012 at 9:16 AM

No. I get an error. Maybe you can help me.

No i have include 

using Catel.Windows.Controls;

and deleted

using System.Windows.Controls;

The error

Base class of 'MyView' differs from declared in other parts

I think i also have to change something in XAML but i don't no what?

<UserControl x:Class="MyView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:catel="http://catel.codeplex.com"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:ViewModels="clr-namespace:ViewModels"
             d:DesignHeight="406"
             d:DesignWidth="629"
             mc:Ignorable="d">
Coordinator
Apr 18, 2012 at 9:26 AM

Your xaml control definition must be like this:

<catel:UserControl ...

Apr 18, 2012 at 9:30 AM
Edited Apr 18, 2012 at 9:32 AM

So what's the difference to your examples. In the "LogicInBehavior" it's the same like in my code:

<UserControl x:Class="Catel.Examples.AdvancedDemo.Views.LogicInBehavior.HouseView"
..
. >
Coordinator
Apr 18, 2012 at 9:33 AM

You are probably using the MVVM in a behavior, where the behavior is added on a "regular" or "normal" control. This is useful when you don't want to derive from the catel user control (for example, if you are using telerik stuff). However, if you just use the UserControl, it is always recommended to use the Catel user control because it gives you more features out of the box (such as OnDataContextChanged, OnPropertyChanged, OnViewModelChanged, etc).

You can use the catel behaviors in two ways:

  1. As a behavior
  2. As a base class

You are probably looking at 1. You can take a look at this documentation, it explains it all.

Apr 18, 2012 at 9:47 AM

Ok. At my start with catel  i don't know the reasons why to choose LogicInBehavior or in LogicViewbase. So i choose LogicInBehavior, I thought its easier.

No i have two possibility. Change to LogicViewbase or use the DetermineViewModelInstance.  Hmm?!

Coordinator
Apr 18, 2012 at 9:51 AM

The behavior still allows you to use the ViewModelInstance, but you have to handle the event. Otherwise, the UserControl will do this for you. anyway, I still recommend to go for the base user control, but in the end that is fully up to you.

Apr 18, 2012 at 9:53 AM
Edited Apr 18, 2012 at 9:58 AM

Ok.I will think about it.

A question to the behavior: I add this to the constructor of my view. Where is the variable "dataContext" defined?

mvvmBehavior.DetermineViewModelInstance += (sender, e) =>
{
  if (dataContext is Rectangle)
  {
      e.ViewModel = new RectangleViewModel((Rectangle)dataContext);
  }
};

Coordinator
Apr 18, 2012 at 10:06 AM

You should use e.DataContext, it's a mistake in the docs. I will fix that tonight.

Apr 18, 2012 at 10:24 AM

Oh. Good. Thanks.

Apr 19, 2012 at 6:58 AM
Edited Apr 19, 2012 at 6:58 AM

I see the issue [MVVM] ViewManager and thought about an ViewModelManager wich hold references to viewmodel and model.
This way, it is possible to retrieve the viewmodel of a model.

What do you think about?

Coordinator
Apr 19, 2012 at 7:43 AM

Yes, we will introduce something like that, but then for views and view models.

Apr 19, 2012 at 7:52 AM

?!

Yes, i see that in the issue tracker

But my question was what do you think about an viewmodelmanager which holds an reference of model and viewmodel?

Coordinator
Apr 19, 2012 at 8:06 AM
Edited Apr 19, 2012 at 8:07 AM

Edit: whoops, too soon...

You can create an issue, we will make it possible as well.