Multiple models in one viewmodel

Feb 16, 2012 at 9:55 PM

Hi,

I have another question. I am implementing User Interface including views and viewmodels. Models are provided for me by another tema as clean POCO objects, so I am not able to change it or to implement validation on model. So, I map model to view model using Model attribute and model properties by ViewModelToModel. All the validation and stuff is done inside the viewmodel. This is working fine and have no issues.

But I have some questions:

1. I need to implement DataWindow showing information about three different models. For example, the first object is Person with name and PersonId, the second is Car with Tzpe and PersonId (foreign key identifying the owner of car) and third model is House simillar to Car.

What is the best practice to implement window showing all the three models and also being able to edit them? I see two options

a. One viewmodel with multiple properties decorated as Model

b. Datawindow containing three userControls each with separate viewModel

I forgot to mention, that I want to be able to save all the changes made to all models at the same time as well as I need to implement Beginedit and cancelEdit to prevent unwanted changes.

2. I found an issue with BeginEdit. I am not able to call BeginEdit in constructor of viewmodel, if viewmodel contains some property which is not serializable even if this property isn't bound to model and is used only locally or so. How can I exclude this article from BeginEdit serialiyation? Until now I have been forced to use simple properties instead of Catel properties, but this comes with many disadvantages and I would like to find better solution. Could you please help me?

Thank you in advance.

Tomas

P.S. I am sorry I my questions aren't clear enought, but I am Catel and WPF beginner trying to learn something new.

Coordinator
Feb 17, 2012 at 11:01 AM

1) I would only write usercontrols when that piece of logic needs to be re-used somewhere else. Otherwise, you can simply define multiple models on the view model. In fact, you can even declare a Model attribute on a ViewModelToModel property, so you can go pretty wild with the attributes :)

Each model property is automatically using IEditableObject (if implemented by the model), no matter how many models you have. So, a call to SaveViewModel will save all models at once.

2) If you have a property defined of a model, you do not have to call BeginEdit manually. It is all done automatically for you. Why would you even want to call BeginEdit on the view model itself? It is probably the models that you want to revert, not the view models.

No worries about the questions, just keep them coming.

Feb 20, 2012 at 5:57 AM

1) Ok, thats clear, but see the second part.

2) Not sure, if I understand. As I have written earlier, my model is a POCO object that doesn't implement IEditableObject, it does implement nothing. So, when I was trying to construct viewModel of such a model, the revertion didn't work. Hitting the Cancel button caused model to be saved in the current state. To solve this, I call BeginEdit in the constructor of corresponding viewModel and I override the viewModel's Cancel method and call CancelEdit there. This works fine but I am not able to use properties that can not be serializable.

I realize that maybe I should use some kind of wrapper to be able to implement IEditableObject directly to the model, but this is not possible. I can live with this issue, but I believe it is not necessary as You  know how to solve it :-)

Many thanks.

Tomas

Feb 20, 2012 at 9:09 AM

Hi

I had the same question and Geert was so kind to add support for this.

You basically have to use the ViewModelToModel.Explicit mode (see documentation). If you use the "Explicit" mode, the Model is not automatically updated when the ViewModel is 'saved'. You have to take control over this. 

In the definition of the model do:

[ViewModelToModel("xxx", "yyy", Mode = ViewModelToModelMode.Explicit)]
public Yyy Xxx

Then in the method that handles the "Save" logic add a call to UpdateExplicitViewModelToModelMappings();

I believe you also need to call CloseViewModel(true) from the "Save" logic and CloseViewModel(false); from the "Cancel" logic to commit or rollback the ViewModel edits. 

Feb 28, 2012 at 6:10 AM

Thank you, GEERTD, this helped for a while.

I have one more question, maybe a little bit OT, but I don't want to flood the whole forum with my topics.

As I have written earlier, I have some POCO objects I want to use in mz viewmodel. As I need to use more of different models at the same time (in one viewModel) and I also want to enjoy the power of Catel, I decided to construct the "superclass" inheriring from DataObjectBase, where all the POCO object models come together. My class is something like this:

    public class PersonWithAddresses : DataObjectBase<PersonWithAddresses>
{
      
        public Person Person
        {
            get { return GetValue<Person>(PersonProperty); }
            set { SetValue(PersonProperty, value); }
        }

         public static readonly PropertyData PersonProperty = RegisterProperty("Person", typeof(Person), () => new Person());

        
        public ObservableCollection<Address> AddressCollection
        {
            get { return GetValue<ObservableCollection<Address>>(AddressCollectionProperty); }
            set { SetValue(AddressCollectionProperty, value); }
        }

        
        public static readonly PropertyData AddressCollectionProperty = RegisterProperty("AddressCollection", typeof(ObservableCollection<Address>), () => new ObservableCollection<Address>());

}

Neither Person, nor Address objects are serializable (POCO). I think you know this is not working because when I define property of PersonWithAddresses type as a model, It gives me an error that Person can not be serializable and If I mark it with [NonSerialized] attribute, I get the same error for Address and here It doesn't even help to exclude the collection from serialiyation.

My question is: Is there any way to work with POCO objects in Catel? How should I wrap them so I can use them as model? Does every object need to inherit from DataObjectBase?

Thank you for your answers.

Tomas

Coordinator
Feb 28, 2012 at 8:56 AM

The serialization is used to either save (when using the SavableDataObjectBase) or backup values. If a property is not serializable, you can simply let this know via the property registration.

Not every property has to be serializable, but then you have to decorate your class with the [AllowNonSerializableMembers] attribute.

Feb 28, 2012 at 9:27 AM

Yes, I know this. But the issue is that even if I do this:

[NonSerialized]
public
static readonly PropertyData AddressCollectionProperty = RegisterProperty("AddressCollection", typeof(ObservableCollection<Address>), () => new ObservableCollection<Address>());

and also use [AllowNonSerializableMembers] I still get an error saying that Address object (which I don't use directly, only in the collection) is not serializable and this is the thing I can't resolve as I can't make Address serializable. I wonder, vhy is catel trying to serialize Address even if the collection is decorated as NonSerialized.

Coordinator
Feb 28, 2012 at 2:02 PM

You should not decorate the PropertyData (it's just the registration). You should decorate the property itself with the [NonSerialized] attribute. Do you have a small repro that we can use in unit tests? Then we will make sure this works in 3.0.

Feb 28, 2012 at 2:12 PM

This is not possible as [NonSerialized] attribute is valid only on field declarations. I will make some small sample to demonstrate the problem ASAP.

Coordinator
Feb 28, 2012 at 2:20 PM

You are right, sorry for not double-checking this. If you send us a solution, we will see what we can do.

Mar 1, 2012 at 8:31 AM

Ok, I created sample solution and uploaded it to Issue Tracker, the exception is thrown to MessageBox in MainWindowVievModel. The demo does actually nothing, just represents the mapping problem. As I was testing my real solution I realized that maybe I can live with this exception and handle it some way but I wonder why is this happening and how to resolve it instead of simply catching it not knowing where it is from.

Thank you for all your work and interest in solving our problems.

Tomas

Coordinator
Mar 1, 2012 at 5:40 PM

Issue solved, we will release a new beta for 3.0 via NuGet tonight.

Mar 1, 2012 at 6:36 PM

Many thanks.

Tomas

Coordinator
Mar 1, 2012 at 8:18 PM

Beta has been released, have fun :)

Mar 2, 2012 at 7:23 AM

Not so much fun :-(

I have downloaded the new version, but I found a problem, not sure, if you are aware of it. In Catel 3.0, you divided Catel.Windows.Controls namespace into two assemblies and this seems to be problem when trying to use components from both assemblies together (e.g. StackGrid in UserControl). This is caused by fact that XAML parser can't handle one namespace within two assemblies (in my opinion). Am I true? Or am I missing something?

Thanks.

Coordinator
Mar 2, 2012 at 8:48 AM

You did read this guide?

http://catel.catenalogic.com/3.0-beta/index.html?whats_new_in_catel_3_0.htm

There are many breaking changes in Catel 3.0 (but for a good cause). In the demos, we use stackgrids and user controls together, it all works fine (all examples are already converted to 3.0).

Make sure to uninstall all deprecated packages (Catel.Windows, Catel.Silverlight, Catel.WP7). Now, the assemblies are equal, no matter what target framework you use:

  • Catel.Core
  • Catel.MVVM
  • Catel.Extensions.Controls

So, I hope we can still turn this into fun.

Mar 2, 2012 at 9:15 AM

Sorry, I have recently downloaded new examples and I see it is working. Yes, I have read the guide and implemented all the required changes. I have also read almost all of the documentation. But when I see it is working for you I have to solve it on my site. Really sorry for bothering you with ma issues.

BTW, many thanks for Prism extension and example, I am going to deploy it as soon as I fix 2.5to3.0 migration issue.

Mar 16, 2012 at 11:42 AM

Geert, one more report pointing the serialization issue mentioned above (collections of nonserializable objects).

Yes, it has been ALMOST solved :-) But the problem is still there if the collection is of count 0 - initialized, but with no items. I mean:

 

public ObservableCollection<Address> AddressCollection
        {
            get { return GetValue<ObservableCollection<Address>>(AddressCollectionProperty); }
            set { SetValue(AddressCollectionProperty, value); }
        }

        
        public static readonly PropertyData AddressCollectionProperty = RegisterProperty("AddressCollection", typeof(ObservableCollection<Address>), () => new ObservableCollection<Address>());

 

will cause the SerializationException when creating new model. However, the code

        public static readonly PropertyData AddressCollectionProperty = RegisterProperty("AddressCollection", typeof(ObservableCollection<Address>), () => new ObservableCollection<Address>{new Address()});

will not. Why is it so?

Tomas

Coordinator
Mar 16, 2012 at 12:40 PM

I will write a unit test and try to find out.

Mar 19, 2012 at 9:23 AM
Edited Mar 19, 2012 at 10:21 AM

Geert, I am facing one more issue related to this topic. Let's look at the example discussed above:

I have object of type PersonWithAddresses (inheriting DataObjectBase) declared as model in viewmodel. Then, I have a Person property mapped to this model via ViewModelToModel atttribute (Person is POCO object). In the view, I bind some textbox to data like this: {Binding Person.Name}. Every thing is ok - values are displayed and also set correctly. The only thing I am not able to achieve is how to test whether the model (PersonWithAddresses) has been changed. The reason is that setting Person.Name doesn't make PersonWithAddresses dirty as the Name property doesn't fire RaisePropertyChanged (because the Person class can neither implement interface, nor inherit form DataObjectBase). Do you have any suggestions have to solve this? I have some ideas, but I don't think either of them correct:

1. I could make wrapper for every POCO object being used and MANUALLY (writing all the getters and setters) wrap the porperties of POCO into Catel properties. But this wold take too much effort in case of hundreds of models each with about ten preoperties.

2. Instead of inheriting from DataObjectBase, my PersonWithAddresses class could inherit from ViewModelbase. In this case, I would be able to declare Person as model and simply map properties in standard way (ViewModelToModel). Then, in the "real" viewmodel, I would just declare the PersonWithAddresses as model. I think this should work, but I don't like the idea of using the ViewModelBase class the way it is not supposed to.

What are your suggestions?

Thanks in advance.

Tomas

Coordinator
Mar 19, 2012 at 11:05 AM

This situation indeed sucks, that's why we introduced the DataObjectBase. It automatically detects children and whether they change. However, for the moment this is not the right solution for you.

1) Not an option

2) Not an option

As long as you don't reference back to the parent model, you should be able to use the DataObjectBase. However, if you start thinking about this situation: why even mark a related parent object as dirty, while only the address is dirty? Most developers "solve" this issue by creating a model window or something like that for child elements, and save that.

Mar 19, 2012 at 12:30 PM

Yes, you are right that only address should be dirty, but as it is POCO object, I have no IsDirty or whatever on it. I am even not aware of properties change, as the property fires no event, when changed. This is why I am looking for solutions even If I absolutely realize that they won't be simple.

To reproduce the whole scenario: I have some kind of master-detail view with detail editable. Detail contains many details stored in different locations (tables) thus many objects (Person, Address, TaskList,..). User should be able to edit all the values and then save or discard changes. So, If the user edits some fields and tries to change view (selecting different person from master list), he should be notified that his changes has not yet been saved. This is the task I am supposed to do. Neither customer, nor backend-provider isn't open for any compromises.

When I was asking for the best practice to show the detail like this, you have recommanded the way of multiple models instead of multiple views with their own viewModels. I admit my question wasn't clear enough so you have suggested your best.

To sum up, I see that there is probably no option to continue this way. Maybe I could go back and think about nested views with own viewmodels.

Tomas

Coordinator
Mar 21, 2012 at 12:44 PM

If there is no property changed on your models, you will have a very hard time checking out whether a model is dirty. The only thing you can do is override OnPropertyChanged on the VM and set IsDirty to true manually as soon as a mapped property has changed.

Then, you can check if (viewModel.IsDirty) => ask user or save immediately.