1

I'm trying to add a DataGrid, where clicking the column header causes an action to be done on all cells below it. I can set an event handler on header click events. But since the headers are bound to string constants in the VM, their value doesn't seem to be passed along to the event handler. I can't find the value of the clicked header anywhere in the "sender" object in the event handler. Any ideas?

Here's a screenshot of the DataGrid:

enter image description here

XAML:

<DataGrid Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Path=InfoFieldCollection, Mode=TwoWay}"
            AutoGenerateColumns="False" CanUserAddRows="False">
    <DataGrid.Resources>
        <Style TargetType="DataGrid">
            <EventSetter Event="SelectionChanged" Handler="RovIllustrationInfoFieldDataGrid_SelectionChanged" />
        </Style>
        <Style TargetType="DataGridCell">
            <Setter Property="FocusVisualStyle" Value="{x:Null}" />
            <Setter Property="BorderThickness" Value="0" />
        </Style>
        <Style TargetType="DataGridColumnHeader">
            <EventSetter Event="Click" Handler="RovIllustrationInfoFieldDataGridColumnHeader_Click" />
        </Style>
    </DataGrid.Resources>
    <DataGrid.RowHeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow},
                            Converter={StaticResource DataGridRowToHeaderStringConverter}}" />
        </DataTemplate>
    </DataGrid.RowHeaderTemplate>
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=DataContext.InfoFieldIsVisibleLabel, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
                </DataTemplate>
            </DataGridTemplateColumn.HeaderTemplate>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding Path=IsVisible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

C#:

private void RovIllustrationInfoFieldDataGridColumnHeader_Click(object sender, RoutedEventArgs e) {
    var header = sender as DataGridColumnHeader;
    var vm = DataContext as ElementDetailsUserControlVM;
    if (header != null && vm != null) {
        Console.WriteLine(header.Content.ToString());
    }
}
Oystein
  • 1,232
  • 11
  • 26
  • What is the result of Console.WriteLine(header.Content.ToString()); ? – o_weisman Nov 16 '17 at 11:48
  • Content's null, so an Exception from the ToString() method. When setting , Content gets set to "Foo". But then I can't bind to the string constants in the VM, and have to define them in the XAML. – Oystein Nov 16 '17 at 11:51
  • Yes, it's definitely null. I've searched the sender object several times, but cannot find any header anywhere, so long as it's bound to a property in the VM. If the header is set to a text string in the XAML, it gets passed to the click event handler just fine. So I'm setting the strings in the XAML for now, throwing exceptions left and right if something is either undefined or defined to unexpected values. So in that case I will at least find out about it quickly. :-) – Oystein Nov 16 '17 at 12:07
  • Do you want to handle click for all headers? – FoggyFinder Nov 16 '17 at 12:40
  • is it actions related to VM, isn't? – FoggyFinder Nov 16 '17 at 12:46
  • Yes, the idea is that clicking any column header sets or clears all the checkboxes in the column below the header. This works fine by just ForEach'ing through all the elements in the `InfoFieldCollection` in the VM. But I need to determine which column header that has been clicked, to determine which property is to be set/cleared. – Oystein Nov 16 '17 at 12:58

1 Answers1

1

You must set DataGridTemplateColumn.Header at first. You can set your header text binding (from DataContext) to your DataGridTemplateColumn.Header and bind your TextBlock.Text to the related DataGridTemplateColumn.Header.

It seems currently you are setting DataGridTemplateColumn.HeaderTemplate and viewing header by using a TextBlock, But you are setting TextBlock.Text Property directionally through a binding, Rather than DataGridTemplateColumn.Header property. Therefore when user clicks your column's header, and you get the 'sender' as Column Header in code, its header.Content has its default value, null.

lifestyle
  • 188
  • 1
  • 12
  • I think I've tried what you suggest, but I get stuck on the error message "Cannot find governing FrameworkElement or FrameworkContentElement for target element." Think it's related to this: https://stackoverflow.com/questions/7660967/wpf-error-cannot-find-governing-frameworkelement-for-target-element – Oystein Nov 16 '17 at 13:26
  • I think this way works fine without exception. Simply test this solution with a plain (constant) header text `` if worked, then your new problem is actually in your column header property Binding. – lifestyle Nov 16 '17 at 13:32
  • "But you are setting TextBlock.Text Property directionally through a binding, Rather than DataGridTemplateColumn.Header property." From what I experience, you cannot set the DataGridTemplateColumn.Header property using a binding. See the link I posted. If I'm wrong, please post a working example. – Oystein Nov 16 '17 at 13:57
  • @Oystein Wich target .net framework version you are using for your project? – lifestyle Nov 16 '17 at 14:46
  • @Oystein Also as an alternative solution here you can Iterate over DataContext items and create `DataGridTemplateColumns` manually and set each one's `Header` by hand. (from MSDN: `// Create a new template column. DataGridTemplateColumn templateColumn = new DataGridTemplateColumn(); templateColumn.Header = "Due Date"; templateColumn.CellTemplate = (DataTemplate)Resources["dueDateCellTemplate"]; templateColumn.CellEditingTemplate = (DataTemplate)Resources["dueDateCellEditingTemplate"]; templateColumn.SortMemberPath = "DueDate";`) – lifestyle Nov 16 '17 at 15:11