Context when adding new items in Catel MVVM

In Catel.MVVM: In the case when adding a new Customer where a property Id1 is still null, the IdDocumentView would have an IdDocumentViewModel context when there is a match on a constructor (one that does not have an IdDocument parameter) leading to a ViewModel with no Model. Is there a standard way of working with this scenario, maybe by setting customer.Id1 to an instance of IdDocument with default values so that the View has a context allowing for binding to update the values of the IdDocument instance?

Structure:

MainView : Catel.Windows.Controls.UserControl

_|--CustomerWindow : Catel.Windows.Window

___|--IdDocumentView : Catel.Windows.Controls.UserControl

Model Classes:

public class Customer : Entity
{
    [DomainSignature]
    public string Code { get; set; }

    public Gender Gender { get; set; }

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string MiddleName { get; set; }

    public DateTime DateOfBirth { get; set; }
    public Lookup PlaceOfBirth { get; set; }

    public string MobileNumber { get; set; }
    public string Email { get; set; }

    public ICollection<CustomerAddress> Addresses { get; set; }

    public Lookup Occupation { get; set; }

    public IdDocument Id1 { get; set; }

    public IdDocument Id2 { get; set; }

}

public class IdDocument : Entity
{
    [DomainSignature]
    public Lookup IdType { get; set; }

    [DomainSignature]
    public string IdCode { get; set; }

    [DomainSignature]
    public DateTime IdExpiry { get; set; }
}

In CustomerView

<DockPanel HorizontalAlignment="Stretch" Grid.ColumnSpan="2">
    <GroupBox Header="ID 1" DockPanel.Dock="Top" Margin="0">
        <local:IdDocumentView DataContext="{Binding Id1}" HorizontalAlignment="Stretch" />
    </GroupBox>
</DockPanel>
<orccontrols:EmptyColumn />
<DockPanel HorizontalAlignment="Stretch" Grid.ColumnSpan="2">
    <GroupBox Header="ID 2" DockPanel.Dock="Top" Margin="0">
        <local:IdDocumentView DataContext="{Binding Id2}" HorizontalAlignment="Stretch" />
    </GroupBox>
</DockPanel>

IdDocumentView

<catel:UserControl x:Class="CTT.MTS.Views.IdDocumentView"
                   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                   xmlns:local="clr-namespace:CTT.MTS.Views"
                   xmlns:catel="http://schemas.catelproject.com"
                   xmlns:orccontrols="http://schemas.wildgums.com/orc/controls"
                   xmlns:app="clr-namespace:CTT.MTS.Model"
                   xmlns:controls="clr-namespace:CTT.MTS.Controls"
                   mc:Ignorable="d"
                   d:DesignHeight="800" d:DesignWidth="800">
    <orccontrols:StackGrid>

        <!-- Row definitions -->
        <orccontrols:StackGrid.RowDefinitions>
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
        </orccontrols:StackGrid.RowDefinitions>

        <!-- Column definitions -->
        <orccontrols:StackGrid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="*" />
        </orccontrols:StackGrid.ColumnDefinitions>

        <Label Content="Type" />
        <ComboBox ShouldPreserveUserEnteredPrefix="False" IsEditable="False" ItemsSource="{Binding IdTypes}"
                  DisplayMemberPath="Value" SelectedValuePath="Value" SelectedValue="{Binding IdTypeText}" Width="100"
                  HorizontalAlignment="Left">
        </ComboBox>
        <Label Content="Code" />
        <TextBox Text="{Binding IdCode}" Width="100" HorizontalAlignment="Left"></TextBox>

        <Label Content="Expiry" />
        <orccontrols:DatePicker Width="120" HorizontalAlignment="Left" Margin="0" Height="40"
            Value="{Binding IdExpiry, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
        </orccontrols:DatePicker>
    </orccontrols:StackGrid>
</catel:UserControl>

in MainViewModel in a command method for a Button click

 await uiVisualizerService.ShowAsync<CustomerViewModel>(new Customer
{
    DateOfBirth = new DateTime(1981, 8, 8),
    Gender = Gender.Male,
    FirstName = "Muhammad",
    Id1 = new IdDocument { IdType = idType, IdCode = "1111", IdExpiry = DateTime.Now.AddYears(-1) },
    //Id2 = new IdDocument(),
    Addresses = new List<CustomerAddress>(new[]
        {new CustomerAddress {Address = address, AddressType = addressType, IsCurrent = true}})
});

notice Id2's initialization is commented. If left like that null IdDocumentView would have no link to an IdDocument model

IdDocumentViewModel's constructors

public IdDocumentViewModel(ILookupService lookupService)
{

    Argument.IsNotNull(() => lookupService);
    this.lookupService = lookupService;

    IdTypes = lookupService.IdTypes;

}

public IdDocumentViewModel(IdDocument idDocument, ILookupService lookupService) :this(lookupService)
{

    IdDocument = idDocument;
    IdTypeText = idDocument.IdType?.Value ?? "";
}

Note that I added the first less arguments constructor after noticing the constructor with an IdDocument parameter not being called for Customer.Id1 when it is null.

Any ideas?

728x90

1 Answers Context when adding new items in Catel MVVM

You assumption is correct that:

  1. Catel will first try to construct a vm with model injection
  2. If that's not possible, it will use the one with the most parameters it can construct
  3. If that's not possible, no VM will be constructed

In such cases, I normally pass in a temporary (new) model without an id. This way, the "child vm" knows it's in create state instead of edit.

5 months ago