Share via


WpfHowTo: Avoid binding error when removing a DataGrid row with RelativeSource (Static Bridge/Relay)

This project shows how to use a Static Bridge or Relay to share a property between Visual Tree elements and elements that have been removed from the UI, due to source data changes. In this example, a forum poster wondered why they got the following error when they deleted an item from the data bound to a DataGrid.

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