1
votes

I'm woking on a project and I have three ViewModels: ObjectDetailsViewMode has a Context (property linking to a model) of type ObjectBase; PropertyTextViewModel has a Context of type PropertyText and PropertyNumberViewModel has a Context of type PropertyNumber.

Below is the structure of the Models:

public class ObjectBase : ModelBase
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set { SetProperty(ref _name, value); }
    }

    public DataCollection<PropertyBase> Properties { get; } = new DataCollection<PropertyBase>();
}

public class PropertyText : PropertyBase
{
    private string _default;
    public string Default
    {
        get { return _default; }
        set { SetProperty(ref _default, value); }
    }
}

public class PropertyNumber : PropertyBase
{
    private double _default = 0;
    public double Default
    {
        get { return _default; }
        set { SetProperty(ref _default, value); }
    }

    private double _minValue = 0;
    public double MinValue
    {
        get { return _minValue; }
        set { SetProperty(ref _minValue, value); }
    }

    private double _maxValue = 0;
    public double MaxValue
    {
        get { return _maxValue; }
        set { SetProperty(ref _maxValue, value); }
    }
}

Regarding the views I have one for each ViewModel. The ObjectDetailsView is a use control that has a TextBox for editing the Object.Name, two buttons to add new PropertyText/PropertyNumber to the Object.Properties and an ItemsControl connected to that Object.Properties.

Each PropertyBase in the ItemsControl (ItemsSource) is resolved into a new view using the DataTemplate marker:

<ItemsControl ItemsSource="{Binding Object.Properties}">
    <ItemsControl.Resources>
        <DataTemplate DataType="{x:Type models:PropertyText}">
            <views:PropertyTextView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type models:PropertyNumber}">
            <views:PropertyNumberView />
        </DataTemplate>
    </ItemsControl.Resources>
</ItemsControl>

As I'm using PRISM the correct ViewModel is automatically created for me and the view DataContext is then set to the new ViewModel. My problem is I need to pass the new Property from the Object.Properties list to the newly created View's ViewModel and store it in the Context property I have there.

I can't avoid creating a View/ViewModel for each property type because there is some under-the-hood logic on some Property types (not the ones I described here.. but I have other types like Boolean, Reference, Enum...)

So I really need to pass a value to the ViewModel I tried to use

<ItemsControl ItemsSource="{Binding Object.Properties}">
    <ItemsControl.Resources>
        <DataTemplate DataType="{x:Type models:PropertyText}">
            <views:PropertyTextView Context="{Binding}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type models:PropertyNumber}">
            <views:PropertyNumberView Context="{Binding}"/>
        </DataTemplate>
    </ItemsControl.Resources>
</ItemsControl>

Be aware that Context is a custom property I created inside the ViewModel's to store the ModelContext. I even created a DependencyProperty in the View's behind code:

    public PropertyBase Context
    {
        get { return (PropertyBase)GetValue(ContextProperty); }
        set { SetValue(ContextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ContextProperty =
        DependencyProperty.Register("Context", typeof(PropertyBase), typeof(PropertyTextView), new PropertyMetadata(null));

But it doesn't get linked to the ViewModels set event (I made a break point there and... nothing). I even tried a SetBinding in the PropertyTextView code-behind (constructor):

string propertyInViewModel = "Context";
var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
this.SetBinding(ContextProperty, bindingViewMode);

No luck with any of these... I' really stuck.

Something More Simple

If the PropertyTextView has this dependency property.

    public string Context
    {
        get { return (PropertyBase)GetValue(ContextProperty); }
        set { SetValue(ContextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Context.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ContextProperty =
        DependencyProperty.Register("Context", typeof(string), typeof(PropertyTextBuilderView), new PropertyMetadata(null));

I should be able to do:

right?! Why isn't the public property "Context" not being called (I placed a breakpoint there and I get nothing).

2
try setting it like : <views:PropertyNumberView Context="{Binding .}"/> with a full stopLuca
@Unlockedluca I tried it.. and doesn't work.. well when I create the instance the GET method in the ViewModel gets called (don't know why) but the SET method doesn't :/xDGameStudios
does your class with your custom Context Property implement INotifyPropertyChanged?Luca
@Unlockedluca it implements BindableBase which is a base class in PRISM that as INotifyPropertyChangedxDGameStudios
does it work if you add for example: <DataTemplate DataType="{x:Type models:PropertyText}"> <TextBlock Text="{Binding DataContext.Default}" </DataTemplate> is then the correct text displayed in the TextBlock?Luca

2 Answers

0
votes

Instead of just setting the Context Property of your View to a new Binding you need to assign the Current DataContext like so:

<views:PropertyNumberView Context="{Binding .}"/>

This should assign the Current Views.DataContext Property to your new View.

If you're in an DataTemplate you probably need to specify the RelativeSource:

<views:PropertyNumberView Context="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType=UserControl}}

<ItemsControl ItemsSource="{Binding Object.Properties}"> <ItemsControl.Resources> <DataTemplate DataType="{x:Type models:PropertyText}"> <views:PropertyTextView Context="{Binding .}"/> </DataTemplate> <ItemsControl.Resources> </ItemsControl>

0
votes

As I'm using PRISM the correct ViewModel is automatically created for me

You don't have to use view-first with Prism. The ViewModelLocator is there to help, if you chose to, but view model-first is possible, too.

If I understand you correctly, you have a view model and want to populate a list with child view models. So do just that:

internal class ParentViewModel : BindableBase
{
    public ParentViewModel( ParentModel parentModel, IChildViewModelFactory factory )
    {
        Children = new object[] { factory.CreateTextViewModel(parentModel.TextProperty), factory.CreateNumberViewModel(parentModel.NumberProperty) };
    }

    public IEnumerable Children { get; }
}

and map the different child view models to child views via DataTemplates.

parentModel.WhateverProperty will have a Name and Value properties as well as setter for the value, probably...