Search code examples
c#wpfdatagrid

Binding a Text property for filtering within a popup that is part of a DataTemplate not working


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?


Solution

  • 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 DataTemplates.

    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".