Carpi Carpi - 3 months ago 75
C# Question

DataGrid Column Header with filter box binding error

After trying for some days I stripped my project down to the minimum to attach it here to my question.

I want to add a text filter box to my WPF datagrid header.
But I get these error messages:

System.Windows.Data Error: 40 : BindingExpression path error: 'TextFilterData' property not found on 'object' ''DataGridColumnHeader' (Name='PART_FillerColumnHeader')'. BindingExpression:Path=TextFilterData; DataItem='DataGridColumnHeader' (Name='PART_FillerColumnHeader'); target element is 'TextBox' (Name='PART_TextFilter'); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'TextFilterData' property not found on 'object' ''DataGridColumnHeader' (Name='')'. BindingExpression:Path=TextFilterData; DataItem='DataGridColumnHeader' (Name=''); target element is 'TextBox' (Name='PART_TextFilter'); target property is 'Text' (type 'String')


The datagrid in my application:

<Grid>
<local:MyDataGrid x:Name="myDataGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="64"
Binding="{Binding Path=id, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, FallbackValue=''}"
local:DataGridColumnExtensions.TextFilterData="">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="ToolTip" Value="The ID of the person"/>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>

<!-- The filter data will be replaced by property binding in the real application. Here I'm using a fix value for the sake of simplicity -->
<DataGridTextColumn Header="Name" Width="400"
Binding="{Binding Path=name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, FallbackValue=''}"
local:DataGridColumnExtensions.TextFilterData="My text filter">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="ToolTip" Value="The name of the person"/>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</local:MyDataGrid>
</Grid>


I'm using an extension class to create the dependency property for the text filter content

public class DataGridColumnExtensions
{
public static readonly DependencyProperty TextFilterDataProperty = DependencyProperty.RegisterAttached("TextFilterData", typeof(string), typeof(DataGridColumn), new FrameworkPropertyMetadata(String.Empty));
public static string GetTextFilterData(DependencyObject target)
{
return (string)target.GetValue(TextFilterDataProperty);
}
public static void SetTextFilterData(DependencyObject target, string value)
{
target.SetValue(TextFilterDataProperty, value);
}
}


And here is the datagrid style I'm using. I tried to remove everything not needed for the sake of simplicity.

<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border x:Name="BackgroundBorder" BorderThickness="0,1,0,1" Background="Gray" BorderBrush="DarkGray" Grid.ColumnSpan="2" />
<ContentPresenter Margin="3" VerticalAlignment="Center" />
<Path x:Name="SortArrow" Visibility="Collapsed" Data="M0,0 L1,0 0.5,1 z" Stretch="Fill" Grid.Column="1" Width="8" Height="6" Fill="White" Margin="0,0,8,0" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5" />
<TextBox x:Name="PART_TextFilter"
Grid.Row="1" Margin="1,0,1,4"
Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridColumnHeader}}, Path=TextFilterData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource Style_HeaderGripper}"/>
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" Style="{StaticResource Style_HeaderGripper}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="SortDirection" Value="{x:Null}">
<Setter TargetName="BackgroundBorder" Property="Background" Value="Transparent" />
<Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="Transparent" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="BackgroundBorder" Property="Background" Value="LightGray" />
<Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="Gray" />
</Trigger>
<Trigger Property="SortDirection" Value="Ascending">
<Setter TargetName="SortArrow" Property="Visibility" Value="Visible" />
<Setter TargetName="SortArrow" Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="180" />
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="SortDirection" Value="Descending">
<Setter TargetName="SortArrow" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<Style TargetType="{x:Type local:MyDataGrid}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyDataGrid}">
<Border Background="{TemplateBinding Background}">
<ScrollViewer Focusable="false" Name="DG_ScrollViewer">
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="3" Background="Gray" BorderBrush="DarkGray" BorderThickness="0,1" />
<Button Command="{x:Static DataGrid.SelectAllCommand}" Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=RowHeaderActualWidth}" Focusable="false" OverridesDefaultStyle="True"/>
<DataGridColumnHeadersPresenter Grid.Column="1"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=HeadersVisibility,
Converter={x:Static DataGrid.HeadersVisibilityConverter},
ConverterParameter={x:Static DataGridHeadersVisibility.Column}}"/>
<ScrollContentPresenter Grid.Row="1" Grid.ColumnSpan="2" CanContentScroll="{TemplateBinding CanContentScroll}" />
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
</Style.Triggers>
</Style>


What is missing to get the filter content binding working?
Any help is highly appreciated.

Answer

In the Header ControlTemplate, you're using RelativeSource Self inside the TextBox

<TextBox x:Name="PART_TextFilter" 
   ...
   Text="{Binding RelativeSource={RelativeSource Self}, Path=TextFilterData, ...}"/>

The error message states there's no TextFilterData property on this TextBox.

Try

Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridColumnHeader}}, 
               Path=Column.TextFilterData, ...}"/>