I have an application that displays a datagrid. However the data has gotten big and I want to incorporate filters to some of the rows. I've gotten as far as creating a DataTemplate for my headers:
<DataGrid>
<DataGrid.Resources>
...
<DataTemplate x:Key="HeaderTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding}" VerticalAlignment="Center"/>
<ToggleButton Name="FilterButton" Grid.Column="1" Content="▼" Margin="2, 1, 1, 1" Padding="1, 0"/>
<Popup IsOpen="{Binding ElementName=FilterButton, Path=IsChecked}" PlacementTarget="{Binding ElementName=FilterButton}" StaysOpen="False">
<Border Background="White" Padding="3">
<TextBox Text={Binding PetNameFilterSearchBox, Mode=TwoWay} Width="300"/> <!--The Text Box I want to bind-->
</Border>
</Popup>
</Grid>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Width="6*" Header="Pet Name" Binding="{Binding PetName}" ElementStyle="{DynamicResource DataGridTextColumnWrap}" HeaderTemplate="{StaticResource HeaderTemplate}"/>
</DataGrid.Columns>
</DataGrid>
So far what it does is show a button next to the header text and when you click on it a small popup window appears containing a text box. The desired effect is that the user can type in the text box and data will be filtered according to what was typed.
In my view model I already have my filter text box property that I want to use for binding:
public string PetNameFilterSearchBox
{
get
{
return _petNameFilterSearchBox;
}
set
{
_petNameFilterSearchBox = value;
RaisePropertyChanged(nameof(PetNameFilterSearchBox));
FilterData(); //As you're writing
}
}
private string _petNameFilterSearchBox = string.Empty;
public CollectionView PetDataFilterView { get; set; }
public bool OnFilterTriggered(object item)
{
if (item is AvailablePetInfo petInfo)
{
var pet_name = PetNameFilterSearchBox;
if (pet_name != string.Empty)
return (petInfo.DisplayName.Contains(pet_name));
}
return true;
}
public void FilterData()
{
CollectionViewSource.GetDefaultView(AvailablePetInfo).Refresh();
}
//Constructor
public PetInfoViewModel()
{
AvailablePetInfo = GetPetInfo();//gets the list
ContactFilterView = (CollectionView)CollectionViewSource.GetDefaultView(AvailablePetInfo);
ContactFilterView.Filter = OnFilterTriggered;
}
When I run my code I see the little button next to the header, I click on it and I see the textbox. But when I start typing I dont see my datagrid updating. I set some breakpoints in my PetNameFilterSearchBox and I find that when I start typing it's not getting hit. This tells me that there's something wrong with the binding. Can someone tell me what I'm doing wrong?
Your problem is one of DataContext
.
I'll be assuming PetNameFilterSearchBox
is a property of the Window
hosting the DataGrid
and that the appropriate DataContext
is set at the Window
level.
Normally, DataContext
is inherited by child elements, so setting the DataContext
for the Window
would set it for all its children. But things change once you start using DataTemplate
s.
In a DataTemplate
, the root DataContext
is always the data object that's being displayed. In your case, that's the string "Pet Name"
. This is why you can put <ContentControl Content="{Binding}"/>
inside the DataTemplate
and have it display "Pet Name"
.
The downside is you can't put <TextBox Text="{Binding PetNameFilterSearchBox}"/>
and expect it to bind to the Window
, because that DataContext
is being overridden by the DataTemplate
.
Normally, you can get around the DataTemplate
DataContext
problem by using RelativeSource
, which you can use walk up the visual tree and find another source to bind to. But this doesn't work inside a Popup
because a Popup
is not actually part of the Window
's visual tree.
What will work is ElementName
:
<TextBox Text="{Binding PetNameFilterSearchBox, Mode=TwoWay, ElementName=W}" Width="300"/>
In the above example, I set on my Window
Name="W"
.