0

I have a ViewModel which has an ObservableCollection<MyData> MainCollection and MyData SelectedData that holds the currently selected item from the MainCollection. MyData consists of several properties and also of an ObservableCollection<MyValuePair> MyPairs. MyValuePair consists of 2 properties Description and Value just like a KeyValuePair (Cannot use a Dictionary because I want to have TwoWay binding).

I want to dynamically create controls foreach MyValuePair according to my DataTemplate (I am trying to do it like here: Adding controls dynamically in WPF MVVM):

        //do not want to create another viewModel
        <DataTemplate DataType="{x:Type vm:MyValuePairViewModel }">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            //problem can only select one MyValuePair at a time
            <TextBlock Text="{Binding MyValuePair.Description, Mode=TwoWay}"/>
            <TextBox Template="{StaticResource TextBoxBaseControlTemplate}" Grid.Row="0" Grid.Column="1" Text="{Binding MyValuePair.Value, Mode=TwoWay}"/>
        </Grid>
    </DataTemplate>

MyData:

public class MyData
{
    //Properties
    ObservableCollection<MyValuePair> MyPairs { get; set; }
}

MainViewModel:

public class MainViewModel : ViewModelBase
{
    ObservableCollection<MyData> MainCollection { get; set; }

    MyData selectedData
    public MyData SelectedData
    {
        get { return selectedData; }
        set
        {
            Set(() => SelectedData, ref selectedData, value);
        }
    }
}

MyValuePair:

public class MyValuePair
{
    private string description;
    public string Description
    {
        get { return description; }
        set { description = value; }
    }

    private string _value;
    public string Value
    {
        get { return _value; }
        set { _value = value; }
    }

but the problem is I do not want to create another ViewModel (MyValuePairViewModel) for my MyValuePair because then I have to synchronize it with my MainViewModel everytime the SelectedData changes or if the MyPair (of type MyValuePair) changes I have to sync it back to the MainViewModel and it does not feel right.

So is there an easy way to bind my DataTemplate directly to each element inside of the ObservableCollection<MyValuePair> property in SelectedData so that the DataTemplate is created for each MyPairs?


Edit At the moment I am using another ViewModel:

public class MyValuePairViewModel : ViewModelBase
{
    private MyValuePair myValuePair;
    public MyValuePair MyValuePair
    {
        get { return myPair; }
        set
        {
            Set(() => MyValuePair, ref myValuePair, value);
        }
    }

    public MyValuePairViewModel(MyValuePair myValuePair)
    {
        MyValuePair = myValuePair;
    }
}

and added it inside my MainViewModel like this:
public ObservableCollection<MyValuePairViewModel> MyPairs { get; set; }.
So for every element inside MyPairs I create the above DataTemplate

Whenever the selection of the ListView changes I do the following:

public void RefreshKeyValuePairs()
    {
        if (MyPairs != null)
        {
            MyPairs.Clear();
        }

        if (SelectedData != null)
        {
            foreach (MyValuePair item in SelectedData.MyPairs)
            {
                MyValuePairViewModel vm = new MyValuePairViewModel(item);
                MyPairs.Add(vm);
            }
        }
    }

However, in this case whenever I type something inside the created TextBox it is not updated inside the MainViewModel's SelectedData only inside MyValuePairViewModel and I have to manually sync it inside the MyValuePairViewModel Set method to the MainViewModel's SelectedData (but this also requires that I have the instance of the MainViewModel inside MyValuePairViewModel).

In other words I want to bind directly to the created elements SelectedData.MyPairs[0].Description bind to first created TextBlock, SelectedData.MyPairs[1].Description bind to second created TextBlock and so on so that it automatically updates in both directions. If SelectedData.MyPairs were not a collection but a normal property I could bind to it directly.

creativeDev
  • 1,113
  • 2
  • 13
  • 20
  • I don't follow the question really. Using a listbox like that templates out each item into the controls in your itemtemplate. Maybe the answer is to use the current item binding / and set IsSynchronizedWithCurrentItem true on the listbox. Bind to the default view of your observablecollection. – Andy Apr 06 '18 at 16:07
  • 1
    I think you should change your bindings in the data template. Instead of SelectedData.MyPairs.Description use Description, and do the same for the Value property – Michael Daniloff Apr 06 '18 at 17:33
  • @Andy Tried to clarify it a little bit. – creativeDev Apr 06 '18 at 18:08
  • 1
    Dr WPF built something called an observable dictionary. I think that does bind two ways. Been a while since I used one. drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/ – Andy Apr 06 '18 at 18:21

1 Answers1

1

You can bind your UI element directly to the MyValuePair object. You do not need a specific ViewModel class MyValuePairViewModel.

The only thing that you will miss is the implementation of INotifyPropertyChanged on MyValuePair. The UI will be initialized normally, but programmatic updates to the Description and Value properties after the initialisation will not appear in the UI. This won't be a problem, if you don't make any programmatic updates.

If you are going to make changes to the Description and Value properties, the easiest thing would be to implement the INotifyPropertyChanged directly on MyValuePair. This only requires a few lines of code and would be easier than duplicating the functionality with a separate class MyValuePairViewModel.

Phil Jollans
  • 3,605
  • 2
  • 37
  • 50
  • I am unable to get the binding correct (it displays only the strings and not the controls with the bound text). Currently I have got `I` and changed the binding of the DataTemplate to `Description` and `Value` (removed `MyValuePair.`). What I also do not understand is what do I use as my target type in my DataTemplate when I am not using `MyValuePairViewModel `? – creativeDev Apr 08 '18 at 11:58
  • Now I used inside my DataTemplate just the model `m:MyValuePair` and it works but I am still creating instances of `MyValuePairViewModel` for every ValuePair inside SelectedData.MyPairs. Although it works I am not sure if it is the way you suggested to bind it directly to the UI. – creativeDev Apr 08 '18 at 12:23
  • But I think I like it more to bind it to a viewModel (model stays clean) but what I do not like is the binding of the DataTemplate is working with the model `m:MyValuePair` but not with the viewModel `vm:MyValuePairViewModel` (I used `Description` as well as `ValuePair.Description`). Why? Is it because `MyPairs` is of type `MyValuePair` and not `MyValuePairViewModel`? – creativeDev Apr 08 '18 at 13:09