DataTrigger in CellTemplate binding to HeaderTemplate; can it work?
Asked Answered
Q

2

2

The goal here would be to check all grid checkboxes if the header checkbox changes:

<Window.Resources>

    <Style TargetType="CheckBox" x:Key="InnerBox">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Style.Triggers>
            <DataTrigger Value="True"
                         Binding="{Binding IsChecked, 
                         ElementName=HeaderCheckbox}">
                <Setter Property="IsChecked" Value="True" />
            </DataTrigger>
            <DataTrigger Value="False"
                         Binding="{Binding IsChecked, 
                         ElementName=HeaderCheckbox}">
                <Setter Property="IsChecked" Value="False" />
            </DataTrigger>
        </Style.Triggers>
    </Style>

</Window.Resources>

<DataGrid>
    <DataGrid.Columns>

        <!-- col1 -->
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.HeaderTemplate>
                <DataTemplate>
                    <!-- header check -->
                    <CheckBox Name="HeaderCheckbox" />
                </DataTemplate>
            </DataGridTemplateColumn.HeaderTemplate>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <!-- body check -->
                    <CheckBox Style="{StaticResource InnerBox}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

        <!-- col2 -->
        <DataGridTextColumn Binding="{Binding}" Header="Text" />
    </DataGrid.Columns>

    <!-- sample data -->
    <sys:String>1</sys:String>
    <sys:String>2</sys:String>
    <sys:String>3</sys:String>
</DataGrid>

Looks like:

Screen

For some reason, the trigger does not fire.

Any ideas?

Quirk answered 30/8, 2011 at 15:44 Comment(0)
J
5

ElementName binding inside a DataTemplate can't reach an element outside of the template as you noticed. This is because it can be instantiated many times and has its own namescope so any ElementName binding you create inside a DataTemplate will look inside the template for another element with that name.

Looking at it with Snoop we can also see that a RelativeSource binding can't be used directly since they are in different parts of the Visual Tree

enter image description here

The only thing that I can think of to get around this is to bind both of the CheckBoxes to a common ancestor, e.g. the parent DataGrid and use an attached property or the Tag property. Example

<Style TargetType="CheckBox" x:Key="InnerBox">
    <Setter Property="HorizontalAlignment" Value="Center" />
    <Setter Property="IsChecked" Value="False" />
    <Style.Triggers>
        <DataTrigger Value="True"
                     Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
                                       Path=Tag}">
            <Setter Property="IsChecked" Value="True" />
        </DataTrigger>
    </Style.Triggers>
</Style>

and

<DataTemplate>
    <!-- header check -->
    <CheckBox Name="HeaderCheckbox"
              IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
                                  Path=Tag,
                                  Mode=OneWayToSource}"/>
</DataTemplate>
Jackqueline answered 30/8, 2011 at 16:37 Comment(1)
Clever, and if Tag is used, you can add an Attached Property.Quirk
A
0

I don't think regular DataBinding to the HeaderCheckBox is possible because the CheckBox exists as part of a Template, and it is in a different branch of the VisualTree than the DataGridItems

Usually I make it the reverse: When the header CheckBox gets checked, check all the row CheckBoxes. My main reason for this is because the CheckBoxes are usually there so users can check/uncheck them, and if they're bound to the header CheckBox checked state, then the user can't alter them.

For implementing that, I usually hook into the Click or Checked event of the Header CheckBox.

If the row CheckBox.IsChecked state is bound to something in a ViewModel, I'll hook the event to a Command in my ViewModel, and set the data item that the CheckBox.IsChecked is bound to to true/false depending on the header CheckBox state (usually passed in as a CommandParameter)

If the CheckBox.IsChecked state is not bound to anything, you can use regular code-behind to loop through your DataGrid.Items, use the ItemContainerGenerator to get the ItemContainer for each item, find the CheckBox, and then set it's check state.

Airedale answered 30/8, 2011 at 15:55 Comment(3)
I'm sorry, your answer doesn't make sense to me. The question is about DataTriggers in the CellTemplate binding to the HeaderTemplate. I'm not using Commands or a ViewModel in the question. I guess I am really just wondering if this can be done. I realize using things other than triggers can do the same thing. I'm sorry I wasn't more clear in my question.Quirk
@Jerry I'm sure it's possible, but it would require some highly unusual workarounds, such as a Converter that searches the Visual Tree for the Header CheckBox. I would recommend that you re-evaluate what you are trying to accomplish before heading down that path and check for alternatives.Airedale
I don't think regular DataBinding to the HeaderCheckBox is possible because the CheckBox exists in a Template, and it is in a different branch of the VisualTree than the DataGridItemsAiredale

© 2022 - 2024 — McMap. All rights reserved.