System.Windows.Data Error: 4 : Cannot find source for binding with reference'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.DataGrid',AncestorLevel='1''. BindingExpression:Path=CanUserAddRows; DataItem=null; target element is 'DataGridCell' (Name='');target property is 'NoTarget' (type 'Object')
The problem was being caused by the following, perfectly acceptable trigger on the DataGridCell...
<
MultiDataTrigger
>
<
MultiDataTrigger.Conditions
>
<
Condition
Binding
=
"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=CanUserAddRows}"
Value
=
"False"
/>
<
Condition
Binding
=
"{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}"
Value
=
"True"
/>
</
MultiDataTrigger.Conditions
>
<
Setter
Property
=
"Background"
Value
=
"Gold"
/>
</
MultiDataTrigger
>
The RelativeSource was using
FindAncestor to traverse up the Visual Tree to find the DataGrid.
When an item was removed from the source collection, the DataGrid removes the row.
However, when the row is removed, it triggers the IsSelected binding to update, which causes the MultiDataTrigger to go check the CanUserAddRows property of the DataGrid. But the row is now "orphaned" from the parent control, so FindAncestor
cannot find the source. That is what causes the error.
The answer is a Static Bridge or Relay, whatever you want to call it, there are many uses, this article is to explain the concept for you to adapt as you need.
public
class
Bridge : FrameworkElement, INotifyPropertyChanged
{
public
Bridge()
{
DataContext =
this
;
}
bool
_BoolUserAddRows;
public
bool
BoolUserAddRows
{
get
{
return
_BoolUserAddRows;
}
set
{
if
(_BoolUserAddRows != value)
{
_BoolUserAddRows = value;
RaisePropertyChanged(
"BoolUserAddRows"
);
}
}
}
void
RaisePropertyChanged(
string
prop)
{
if
(PropertyChanged !=
null
)
PropertyChanged(
this
,
new
PropertyChangedEventArgs(prop));
}
public
event
PropertyChangedEventHandler PropertyChanged;
}
It inherits FrameworkElement simply for easy use in XAML and DataContext.
This is then made available to all the Window's controls in the Resources:
<
Window.Resources
>
<
local:Bridge
x:Key
=
"MyBridge"
/>
</
Window.Resources
>
In this example, it is used in the DataGrid to pass out and control the CanUserAddRowsProperty, and is available to the outgoing, orphaned row control triggers via the same StaticResource MyBridge.This available in a demo project here.
<
DataGrid
x:Name
=
"dataGrid2"
ItemsSource
=
"{Binding AllItems2}"
CanUserAddRows
=
"{Binding BoolUserAddRows, Source={StaticResource MyBridge}}"
HorizontalAlignment
=
"Left"
VerticalAlignment
=
"Top"
>
<
DataGrid.CellStyle
>
<
Style
TargetType
=
"{x:Type DataGridCell}"
>
<
Style.Triggers
>
<
MultiDataTrigger
>
<
MultiDataTrigger.Conditions
>
<
Condition
Binding
=
"{Binding BoolUserAddRows, Source={StaticResource MyBridge}}"
Value
=
"False"
/>
<
Condition
Binding
=
"{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}"
Value
=
"True"
/>
</
MultiDataTrigger.Conditions
>
<
Setter
Property
=
"Background"
Value
=
"Gold"
/>
</
MultiDataTrigger
>
</
Style.Triggers
>
</
Style
>
</
DataGrid.CellStyle
>
</
DataGrid
>
This article has also been published on MSDN Samples Gallery as a project you can download and try. It includes two DataGrids, first the broken one, which you can see the error in Visual Studio's Output tab. The second shows the fixed DataGrid, which does not trigger an error when you click to remove a row.
This small article is part of a series of WPF "How To" articles, in response to real user questions on the MSDN WPF Forum.
This available in a demo project here