Melissa
Melissa

Reputation: 135

Binding ComboBox in DataGridTemplateColumn WPF

I am building a WPF MVVM application.

What I have:

I have a DataTable, binded to a DataGrid as such:

<DataGrid
       Name="Map"
       AutoGenerateColumns="True"
       AutoGeneratingColumn="Map_AutoGeneratingColumn"
       IsReadOnly="True"
       ItemsSource="{Binding MapDataTable}" />

Here is how I am creating the data table in the ViewModel:

ObservableCollection<ObservableCollection<MapModel>> MapDataList { get; set; }

private void CreateDataTable(IEnumerable<MapModel> mapDataItems)
{
    MapDataTable.Clear();

    DataTable dataTable = new DataTable("MapDataTable");
    dataTable.Columns.Add("First", typeof(string));
    dataTable.Columns.Add("Second", typeof(string)); //typeof might need to be changed to ObservableCollection<SomeModel> (still works) or ComboBox

    var mapData = new ObservableCollection<MapModel>();

    foreach (var item in mapDataItems)
    {
         mapData.Add(item);
    }

    MapDataList.Add(mapData);

    foreach (MapModel item in mapDataItems)
    {
         DataRow dataRow = dataTable.NewRow();

         dataRow[0] = item.Name;
         dataRow[1] = item.SomeValues; //mistake could be here

         dataTable.Rows.Add(dataRow);
     }

     MapDataTable = dataTable;
 }

MapModel.cs:

public class MapModel
{
    public string Name { get; set; }

    public ObservableCollection<SomeModel> SomeValues { get; set; } = new ObservableCollection<SomeModel>
    {
        new SomeModel(),
        new SomeModel()
    };
}

SomeModel.cs:

public class SomeModel
{
    public string Name { get; set; }

    public double Value { get; set; }

    public SomeModel()
    {
        Name = "test";
        Value = 0;
    }
}

Map_AutoGeneratingColumn in the xaml.cs:

if (e.PropertyName == "Second")
{
      var templateColumn = new DataGridTemplateColumn
      {
            Header = e.PropertyName,
            CellTemplate = (sender as FrameworkElement).FindResource("SecondCellTemplate") as DataTemplate,
            CellEditingTemplate = (sender as FrameworkElement).FindResource("SecondCellEditingTemplate") as DataTemplate
      };

      e.Column = templateColumn;
}

In the View:

<Grid.Resources>
    <DataTemplate x:Key="SecondCellTemplate">
       <TextBlock Text="{Binding Path=???, Mode=OneWay}" />
    </DataTemplate>
    <DataTemplate x:Key="SecondCellEditingTemplate">
       <ComboBox
           DisplayMemberPath="Name"
           ItemsSource="{Binding Path=SomeValues, Mode=OneWay}" //wrong binding
           SelectedValue="{Binding Path=???, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
           SelectedValuePath="Name" />
    </DataTemplate>
</Grid.Resources>

What I want to achieve:

I want the SomeValues' names to be displayed in the ComboBox of the DataGridTemplateColumn.

How do I achieve this?

EDIT:

From this comment (https://stackoverflow.com/a/35212223/17198402) I understand how to bind SomeValues IF the columns were hardcoded and not autogenerated, but I still can't wrap my head around how to do it when they are autogenerated. Setting the ItemsSource in the code-behind is still a mystery to me. As far as I understand, DataGridTemplateColumn doesn't inherit the DataContext, unlike DataGridTextColumn.

EDIT 2:

I was able to bind to a property in my ViewModel this way:

ViewModel.cs

public ObservableCollection<SomeModel> SomeValues { get; set; } = new ObservableCollection<SomeModel>
{
      new SomeModel(),
      new SomeModel()
};

xaml:

<Grid.Resources>
    <DataTemplate x:Key="SecondColumnTemplate" DataType="DataGridCell">
         <ComboBox 
             DisplayMemberPath="Name" 
             ItemsSource="{Binding Path=DataContext.SomeValues, RelativeSource={RelativeSource AncestorType=Page}}" />
    </DataTemplate>
</Grid.Resources>

AutoGeneratingColumn:

 var templateColumn = new DataGridTemplateColumn
 {
       Header = e.PropertyName,
       CellTemplate = (sender as FrameworkElement).FindResource("SecondColumnTemplate") as DataTemplate
 };

 e.Column = templateColumn;

This successfully binds the new property in the ViewModel, but of course that's not what I want. How do I bind to the current item.SomeValues ?

Upvotes: 1

Views: 1056

Answers (1)

Shrimperator
Shrimperator

Reputation: 467

There are some things wrong with your binding:

<Setter Property="ItemsSource" Value="{Binding DataContext.MapDataList.SomeValues, RelativeSource={RelativeSource AncestorType=Page}}" />

First of all, DataContext. is unnecessary whichever way you look at it. DataContext is, by definition, the path WPF bindings look at to resolve their binding by default. If you don't set the DataContext, it is null. Now you can either set the DataContext to something other than null, or you can specify a path for the binding. You're mixing up different ways of binding data here. DataContext. shouldn't be part of your binding in any case, but if you use RelativeSource you'll have to specify the Path of the binding and set it to the property you want to bind to.

The first answer to this question here is a great explanation of DataContext and how to use RelativeSource: How do I use WPF bindings with RelativeSource?

All that aside, your binding cannot work: MapDataList is an ObservableCollection, and the elements of that collection have a list called "SomeValues". If you want to access SomeValues, you're going to have to specify an index of your collection at some point. After all: which element of the collection should the binding go to?

You seem to be mixing up a lot of different systems here.

I recommend you read through these questions and their answers thoroughly to get an idea of how to do this properly:

How to bind collection to WPF:DataGridComboBoxColumn

Binding a WPF ComboBox to a custom list

Upvotes: 1

Related Questions