简体   繁体   中英

WPF MVVM Binding collection to DataTemplate

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM