1

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).

xDGameStudios
  • 321
  • 1
  • 13
  • try setting it like : with a full stop – Luca Feb 08 '18 at 12:05
  • @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 Feb 08 '18 at 12:11
  • does your class with your custom Context Property implement INotifyPropertyChanged? – Luca Feb 08 '18 at 12:16
  • @Unlockedluca it implements BindableBase which is a base class in PRISM that as INotifyPropertyChanged – xDGameStudios Feb 08 '18 at 12:20
  • does it work if you add for example: is then the correct text displayed in the TextBlock? – Luca Feb 08 '18 at 12:41
  • @Unlockedluca Yes it is passed okay!! but I don't want a textblock I need to use my custom usercontrol (PropertyTextView) – xDGameStudios Feb 08 '18 at 13:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/164765/discussion-between-xdgamestudios-and-unlockedluca). – xDGameStudios Feb 08 '18 at 15:04

2 Answers2

0

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>

Luca
  • 1,766
  • 3
  • 27
  • 38
  • But the DataContext should be pointing to the ViewModel and I'm passing a reference to a Model... so the new ViewModel knows what is it's context. If I understand what you're saying then I need to create the ViewModel my self... – xDGameStudios Feb 08 '18 at 12:17
  • which DataContext should point to the ViewModel, so you want to Pass "Object"? – Luca Feb 08 '18 at 12:20
  • If you look at my code I state that ItemsControl's ItemsSource is linked to Object.Properties (that is a ObservableCollection of PropertyBase this is a model not a view model)... then like in a ListView I need to pass each item (PropertyBase) to the new UserControler (PropertyTextView) (and catch it in the ViewModel)... so I can work with it. – xDGameStudios Feb 08 '18 at 12:27
  • ahh, now it's clear, maybe you should include that in your question, I'll update my answer in a sec – Luca Feb 08 '18 at 12:30
  • I can't seem to make it work... If I'm using a DataTemplate for each item doesn't Binding referes to the corresponding property inside Object.Properties... like Object.Properties[n] – xDGameStudios Feb 08 '18 at 12:58
0

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...

Haukinger
  • 10,420
  • 2
  • 15
  • 28
  • My application is like a front view to build a database... for example you want to create a new table named "Persons" and then add properties to it (the equivalent to columns) so the itemscontrol containing the properties depends on the table that is selected and on the properties that are manually added. I'm having a difficult time making this. and I don't seem to understand your help – xDGameStudios Feb 08 '18 at 16:40
  • What I'm saying is just one thing - create your view models directly, do not try to spawn views with automatically created view models and set their contexts afterwards. Put the context in the view model when you create it. – Haukinger Feb 08 '18 at 17:57
  • I think I get what you are trying to say! One question though.. I'm using unity for dependency injection... and I wanted to make use of unity for that. If I'm creating the ViewModels manually and they need some IDataAccess interface I don't want to create it in another ViewModel that as nothing to do with that component. How can I do it... is there a way to create ViewModels on demand?! – xDGameStudios Feb 08 '18 at 20:12
  • Cue: abstract factory. create a class that creates view models and inject that. Then your parent view model can create child view models at will without `new` and the child view models get all their dependencies from the factory... see: https://stackoverflow.com/questions/39768318/how-better-to-resolve-dependencies-in-object-created-by-factory/39788267#39788267 – Haukinger Feb 09 '18 at 06:55