Nested properties

Oct 17, 2011 at 2:35 PM

Hi,

Suppose that I have a model with:

  • Address is a class with Street, Number, City and ZipCode fields.
  • Person class with properties FirstName, LastName and Address.

I want to be able to edit all fields in a single page.

How would I define my PersonViewModel for this based on ViewModelBase.

To be more specific, how would I define the nested properties like Person.Address.Street,... and link them to the [model]?

 

 

Coordinator
Oct 17, 2011 at 2:43 PM

This scenario gets more complex, and depends on what you want. In my opinion, the VM should provide all the properties that a view should edit on that specific view, in this case:

  • FirstName
  • LastName
  • Street
  • Number
  • City
  • ZipCode

So, what you can do with Catel is map models to models. Sounds a bit strange, but here is an example:

/// <summary>
/// Person view model.
/// </summary>
public class PersonViewModel : ViewModelBase
{
    public PersonViewModel(Person person)
    {
                
    }

    /// <summary>
    /// Gets or sets the person.
    /// </summary>
    [Model]
    private Person Person
    {
        get { return GetValue<Person>(PersonProperty); }
        set { SetValue(PersonProperty, value); }
    }

    /// <summary>
    /// Register the Person property so it is known in the class.
    /// </summary>
    public static readonly PropertyData PersonProperty = RegisterProperty("Person", typeof(Person));

    /// <summary>
    /// Gets or sets the person address.
    /// </summary>
    [Model]
    [ViewModelToModel("Person")]
    private Address Address
    {
        get { return GetValue<Address>(AddressProperty); }
        set { SetValue(AddressProperty, value); }
    }

    /// <summary>
    /// Register the Address property so it is known in the class.
    /// </summary>
    public static readonly PropertyData AddressProperty = RegisterProperty("Address", typeof(Address));

    /// <summary>
    /// Gets or sets the first name.
    /// </summary>
    [ViewModelToModel("Person")]
    public string FirstName
    {
        get { return GetValue<string>(FirstNameProperty); }
        set { SetValue(FirstNameProperty, value); }
    }

    /// <summary>
    /// Register the FirstName property so it is known in the class.
    /// </summary>
    public static readonly PropertyData FirstNameProperty = RegisterProperty("FirstName", typeof(string));

    // TODO: map additional person properties

    /// <summary>
    /// Gets or sets the street.
    /// </summary>
    [ViewModelToModel("Address")]
    public string Street
    {
        get { return GetValue<string>(StreetProperty); }
        set { SetValue(StreetProperty, value); }
    }

    /// <summary>
    /// Register the Street property so it is known in the class.
    /// </summary>
    public static readonly PropertyData StreetProperty = RegisterProperty("Street", typeof(string));

    // TODO: map additional address properties
}

Oct 17, 2011 at 3:06 PM

The speed at which you answer is amazing ;-)

Well that was exactly the approach that I thougth of.... Like "it would be really neat if it could be done this way, but that for sure won't work..." I apparently was wrong with that last thought. Nice!

This can indeed become complex (if there is nesting several levels deep...). I don't suppose the nested user control support could help out here?

Oct 17, 2011 at 3:08 PM

With that I mean having a "AddressView" user control with its own AddressViewModel that I plug into every view that needs to edit an address...

Coordinator
Oct 17, 2011 at 3:08 PM

You are lucky we live in the same time zone :)

In that case, you need to create a PersonView, and in there use an AddressView. This is perfectly possible, but then you have less control about the layout (because you have 2 user controls). It's up to you what technique to use, both just work fine. However, if you want to re-use the address for, say Customers, or maybe Organisations, or ..., it might be a good idea to create a separate user control for the Address entity.

Oct 17, 2011 at 3:33 PM

Does that last suggestion also include creating an AddressViewModel?

So that would mean I'd have:

  • PersonViewModel with properties for the Person fields + 1 property for the Address called PAddress, and a SaveCommand to tie to the "Save" button to save the Person (including the address)
  • AddressViewModel with properties for the Address fields
  • UserControl<Address>
  • UserControl<Person> that contains a UserControl<Address> and set the DataContext of that UserControl<Address> to PAddress, which would trigger Catel to automatically create the AddressViewModel
  • ...

Or am I getting this wrong?

 


Coordinator
Oct 17, 2011 at 3:34 PM

You are absolutely right, this is exactly the way it should (at least can) be done!

Oct 18, 2011 at 9:11 PM

I am trying your suggestion to map models to models... (not going the nested views path for now).

It only seems to work when the Person.Address is not null, so I need to do Person.Address = new Addresss() before passing the Person model to the ctor of the viewmodel. If I don't do this, the Person.Address remains null.

Is this a bug or is this by design?

Coordinator
Oct 19, 2011 at 6:28 AM

This is by design. The view model base has absolutely no knowledge on how to create the models. For example, must it be added to a container afterwards, or ... Therefore, the developer itself is responsible for the creation of the models, which then can be used as model in the view model.

Oct 19, 2011 at 7:02 AM

Makes sense. Thanks.