Search code examples
c#wpfxamldata-bindingwpfdatagrid

WPF: DataTrigger not firing


In a WPF Project I have some restyled DataGridColumnHeaders of a DataGrid which show a ComboBox for each DataGridColumnHeader. When the user selects from the ComboBox's the SelectionChanged handler (in the code behind) updates an Array of ColumnOptionViewModel objects on the MainWindowViewModel with the newest selection.

At this point some code also works out if there are any duplicate selections in this array, and then sets an IsDuplicate Boolean property on the ColumnOptionViewModel that are duplicates. The idea is that a DataTrigger picks up the change in IsDuplicate and changes the Background of a TextBlock in the DataTemplate of the ItemTemplate for the duplicate ComboBox's to Red.

However, this trigger is not firing. The IsDuplicate properties are being set ok, and everything else works as expected. What am I doing wrong?

Here is the XAML for the Window:

<Window x:Class="TestDataGrid.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestDataGrid"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">

 <Grid>
    <DataGrid Grid.Row="1" x:Name="dataGrid" ItemsSource="{Binding Records}">
        <DataGrid.ColumnHeaderStyle>
            <Style TargetType="DataGridColumnHeader">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <StackPanel>
                                <ComboBox x:Name="cbo"
                                          ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.ColumnOptions}"
                                          SelectionChanged="cbo_SelectionChanged">

                                     <ComboBox.ItemTemplate>
                                        <DataTemplate>
                                            <TextBlock x:Name="txt" Text="{Binding Name}"/>
                                            <DataTemplate.Triggers>
                                                <DataTrigger Binding="{Binding ElementName=cbo, Path=SelectedItem.IsDuplicate}">
                                                    <Setter TargetName="txt" Property="Background" Value="Red"/>
                                                </DataTrigger>
                                            </DataTemplate.Triggers>
                                        </DataTemplate>
                                    </ComboBox.ItemTemplate>
                                </ComboBox>
                            </StackPanel>   
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </DataGrid.ColumnHeaderStyle>
    </DataGrid>    
</Grid>

CODE BEHIND:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainWindowViewModel(RecordProvider.GetRecords());
    }

    private void cbo_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var vm = (MainWindowViewModel)DataContext;

        var selectionChangedCombo = (ComboBox)e.Source;

        var dataGridColumnHeader = selectionChangedCombo.TemplatedParent as DataGridColumnHeader;

        vm.ColumnSelections[dataGridColumnHeader.DisplayIndex] = selectionChangedCombo.SelectedItem as ColumnOptionViewModel;

        CheckForDuplicates();

    }

    private void CheckForDuplicates()
    {
        var vm = (MainWindowViewModel)DataContext;

        var duplicates = vm.ColumnSelections.GroupBy(x => x.Name)
            .Where(g => g.Skip(1).Any())
            .SelectMany(g => g);

        foreach (var option in duplicates)
        {
            option.IsDuplicate = true;
        }
    }
}

MainWindowViewModel :

public class MainWindowViewModel : ViewModelBase
{
    public ObservableCollection<ColumnOptionViewModel> _columnOptions = new ObservableCollection<ColumnOptionViewModel>();
    public ObservableCollection<RecordViewModel> _records = new ObservableCollection<RecordViewModel>();

    ColumnOptionViewModel[] _columnSelections = new ColumnOptionViewModel[3];

    public MainWindowViewModel(IEnumerable<Record> records)
    {
        foreach (var rec in records)
        {
            Records.Add(new RecordViewModel(rec));
        }

        ColumnOptions.Add(new ColumnOptionViewModel(TestDataGrid.ColumnOptions.ColumnOption1));
        ColumnOptions.Add(new ColumnOptionViewModel(TestDataGrid.ColumnOptions.ColumnOption2));
        ColumnOptions.Add(new ColumnOptionViewModel(TestDataGrid.ColumnOptions.ColumnOption3));

        ColumnSelections[0] = ColumnOptions[0];
        ColumnSelections[1] = ColumnOptions[1];
        ColumnSelections[2] = ColumnOptions[2];

    }

    public ObservableCollection<ColumnOptionViewModel> ColumnOptions
    {
        get { return _columnOptions; }
        set { _columnOptions = value; }
    }

    public ColumnOptionViewModel[] ColumnSelections
    {
        get { return _columnSelections; }
        set { _columnSelections = value; }
    }

    public ObservableCollection<RecordViewModel> Records
    {
        get { return _records; }
        set { _records = value; }
    }
}

ColumnOptionViewModel :

public class ColumnOptionViewModel : ViewModelBase
{
    ColumnOptions _colOption;

    public ColumnOptionViewModel(ColumnOptions colOption )
    {
        _colOption = colOption;
    }

    public string Name
    {
        get { return _colOption.ToString(); }
    }

    public override string ToString()
    {
        return Name;
    }

    private bool _isDuplicate = false;
    public bool IsDuplicate
    {
        get { return _isDuplicate; }
        set
        { _isDuplicate = value;
            OnPropertyChanged();
        }
    }
}

EDIT:

ViewModelBase :

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Solution

  • If you are trying to bind to the IsDuplicate property of the SelectedItem in the ComboBox you could use a RelativeSource.

    You should also set the Value property of the DataTrigger to true/false depending on when you want the Background property of the TextBlock to be set to Red:

    <ComboBox x:Name="cbo" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.ColumnOptions}"
                      SelectionChanged="cbo_SelectionChanged">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock x:Name="txt" Text="{Binding Name}"/>
                <DataTemplate.Triggers>
                    <DataTrigger Binding="{Binding Path=SelectedItem.IsDuplicate, RelativeSource={RelativeSource AncestorType=ComboBox}}" Value="True">
                        <Setter TargetName="txt" Property="Background" Value="Red"/>
                    </DataTrigger>
                </DataTemplate.Triggers>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>