Reputation: 545
Here is the class that is bound to the column. I set a watch on ListParts and the parts do in fact exist.
private MasksSourceList _MasksSourceListBound;
public MasksSourceList MasksSourceListBound
{
get => _MasksSourceListBound;
set { SetAndNotify(ref _MasksSourceListBound, value, () => MasksSourceListBound); }
}
public class MasksSourceList : ObservableObject
{
private List<MaskDetail> _maskDetails;
public List<MaskDetail> MaskDetails
{
get => _maskDetails;
set { SetAndNotify(ref _maskDetails, value, () => MaskDetails); }
}
private List<Jarvis.Data.Models.Parts> _listParts;
public List<Jarvis.Data.Models.Parts> ListParts
{
get => _listParts;
set { SetAndNotify(ref _listParts, value, () => ListParts); }
}
}
Here is the XAML that shows the parent items source of the DataGrid.
<DataGrid ItemsSource="{Binding MasksSourceListBound.MaskDetails}" >
I am showing a normal text column to demonstrate the the ItemsSource binding of the DataGrid is working as shown in the attached image.
<DataGrid.Columns>
<DataGridTextColumn Header="part name" Binding="{Binding PartName}">
</DataGridTextColumn>
<DataGridComboBoxColumn
Header="part name"
ItemsSource="{Binding MasksSourceListBound.ListParts}"
SelectedValueBinding="{Binding PartName, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="PartId"
DisplayMemberPath="PartName"
>
</DataGridComboBoxColumn>
But why does the ItemsSource of the combo box column does not recognize ListParts? I checked the spelling of PartId and PartName and they are correct.
Here is the error message from the Output Window:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=MasksSourceListBound.ListParts; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=64358720); target property is 'ItemsSource' (type 'IEnumerable')
I have tried this instead but still no results:
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding MasksViewModel.MasksSourceListBound.ListParts}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding MasksViewModel.MasksSourceListBound.ListParts}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
After modifying the above using DataContext.MasksSourceListBound.ListParts with the proper Relative Source etc. The tracer shows:
System.Windows.Data Warning: 58 : Path: 'DataContext.MasksSourceListBound.ListParts'
System.Windows.Data Warning: 60 : BindingExpression (hash=20088760): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=20088760): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=20088760): Attach to System.Windows.Controls.DataGridComboBoxColumn+TextBlockComboBox.ItemsSource (hash=46581119)
System.Windows.Data Warning: 66 : BindingExpression (hash=20088760): RelativeSource (FindAncestor) requires tree context
System.Windows.Data Warning: 65 : BindingExpression (hash=20088760): Resolve source deferred
System.Windows.Data Warning: 67 : BindingExpression (hash=5618098): Resolving source
System.Windows.Data Warning: 70 : BindingExpression (hash=5618098): Found data context element: <null> (OK)
Here is the corrected XAML Decided to explicitly set the datacontext for DataGrid to avoid 3 part binding. But still getting Input String was not in correct format in break mode. The output indicates RelativeSource (FindAncestor) requires tree context:
<DataGrid DataContext="{Binding MasksSourceListBound}" ItemsSource="{Binding MaskDetails}"
<DataGridComboBoxColumn Header="part name" HeaderStringFormat=" {0}" Width="200"
SelectedValueBinding="{Binding PartName, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="PartId"
DisplayMemberPath="PartName">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding DataContext.ListParts, RelativeSource={RelativeSource AncestorType=DataGrid}, PresentationTraceSources.TraceLevel=High}"
/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding DataContext.ListParts, RelativeSource={RelativeSource AncestorType=DataGrid}, PresentationTraceSources.TraceLevel=High}"
/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
Upvotes: 2
Views: 729
Reputation: 37059
So we've gotten halfway there. The DataContext
for the bindings in ElementStyle and EditingElementStyle is the row item -- MaskDetail
, in your case. My guess is that this binding:
Value="{Binding MasksViewModel.MasksSourceListBound.ListParts}"
...is meant to suggest that the binding should find a viewmodel of type MasksViewModel
and use the specified property from it.
That's not how bindings work, for one thing. They have a DataContext
and if you don't specify Source
or RelativeSource
explicitly, they look for the property there. What's happening with that binding is it's looking at a MaskDetail
, the row item class that you're populating the grid with, for a property named MasksViewModel
. I don't think there is one, or this would work.
Here's a way to find out why bindings fail:
Value="{Binding MasksViewModel.MasksSourceListBound.ListParts, PresentationTraceSources.TraceLevel=High}"
Then watch the output pane in VS at runtime. You'll get traces for each step as the binding tries to find MasksViewModel.MasksSourceListBound.ListParts
and you'll see where and why it fails.
So what you want to do, in the element styles, is bind to ListParts
, a property of MasksSourceListBound
, which in turn is a property of the main viewmodel -- which is the DataGrid
's DataContext
.
So we search back up the visual tree to the DataGrid
, get its DataContext
, and look there for MasksSourceListBound.ListParts
:
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding DataContext.MasksSourceListBound.ListParts, RelativeSource={RelativeSource AncestorType=DataGrid}}"
/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding DataContext.MasksSourceListBound.ListParts, RelativeSource={RelativeSource AncestorType=DataGrid}}"
/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
It's not beautiful, but it works consistently and that's the way we do things in WPF.
Here's a more complete picture of my XAML. I didn't recreate your whole thing exactly. The part that matters is the relative source binding: I can bind to properties of the DataGrid's DataContext using the following XAML. The DataGrid's DataContext is a reference to the main viewmodel -- the same object that owns MasksSourceListBound.MaskDetails
.
<DataGrid
ItemsSource="{Binding MasksSourceListBound.MaskDetails}"
>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding OtherTestProperty}" />
<DataGridComboBoxColumn
Header="Test"
SelectedItemBinding="{Binding SelectedItem}"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding DataContext.MasksSourceListBound.ListParts, RelativeSource={RelativeSource AncestorType=DataGrid}, PresentationTraceSources.TraceLevel=High}"
/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding DataContext.MasksSourceListBound.ListParts, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
Upvotes: 2