Reputation: 135
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
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