Styling DataGridCell correctly

16,346

Dependency Property Value Precedence

This:

<Grid Background="LightGreen">
    <Grid.Resources>
        <Style TargetType="{x:Type Grid}">
            <!-- Trigger Stuff -->
        </Style>
    </Grid.Resources>
    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>

Needs to be:

<Grid>
    <Grid.Resources>
        <Style TargetType="{x:Type Grid}">
            <Setter Property="Background" Value="LightGreen"/>
            <!-- Trigger Stuff -->
        </Style>
    </Grid.Resources>
    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>

Not sure about your second question as of now, possibly a related problem, i would suggest setting TextElement.Foreground instead of Foreground for starters. Getting Transparent as value is not very helpful, what control template do you use for the DataGridCell? If it is custom, is the Background hooked up properly via a TemplateBinding?

This works as long as the Background property is used, so if you have a ControlTemplate which sets things internally you need to externalize that. A normal DataGrid example:

<DataGrid.CellStyle>
    <Style TargetType="{x:Type DataGridCell}">
        <Setter Property="Background" Value="LightGreen"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Content}" Value="Apple">
                <Setter Property="Background" Value="Red"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Content}" Value="Tomato">
                <Setter Property="Background" Value="Green"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.CellStyle>
<Style TargetType="TextBlock">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Value="Red">
            <Setter Property="Foreground" Value="White"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Value="Green">
            <Setter Property="Foreground" Value="White"/>
        </DataTrigger>
    </Style.Triggers>
    <Setter Property="FontWeight" Value="Bold"/>
</Style>

So if the CellStyle sets the ControlTemplate the properties need to be hooked up via TemplateBinding. e.g.

<DataGrid.CellStyle>
    <Style TargetType="{x:Type DataGridCell}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGridCell}">
                    <Grid Background="{TemplateBinding Background}">
                        <ContentPresenter />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Background" Value="LightGreen"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Content}" Value="Apple">
                <Setter Property="Background" Value="Red"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Content}" Value="Tomato">
                <Setter Property="Background" Value="Green"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.CellStyle>

Do not do the triggering inside the template or it will get messy.

Share:
16,346
Damascus
Author by

Damascus

C#-WPF developer

Updated on June 04, 2022

Comments

  • Damascus
    Damascus almost 2 years

    This is a question following my previous problem, you can find it right there

    So. Now I defined a DataGrid with a specific ElementStyle for each column (which just defines the TextBlocks inside in bold & white -- will come over this problem later)

    So now I have two questions

    First question (solved)

    When I happen to set a background to my cell, it overrides the default style, and the background stays the same when the cell is highlighted.

    One example of a style:

    <!-- Green template for market-related -->
    <ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
        <Grid Background="Green">
            <ContentPresenter
                            HorizontalAlignment="Center"
                                      VerticalAlignment="Center" />
        </Grid>
    </ControlTemplate>
    

    I'd naturally say that this is "normal" because I set the Grid's background to Green. I therefore tried it this way:

    <!-- Light green template for sophis-related -->
    <ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
        <Grid Background="LightGreen">
            <Grid.Resources>
                <Style TargetType="{x:Type Grid}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}}, 
                                                Converter={StaticResource DebugConverter}}" Value="True">
                            <Setter Property="Grid.Background" Value="#FF3774FF" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Grid.Resources>
            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </ControlTemplate>
    

    This won't work either. As you can see I put a DebugConverter so I can check that the trigger is actually called, which is the case, but... Background does not change (and Snoop confirms this...)

    Third try:

    <!-- Light green template for sophis-related -->
    <ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
        <ControlTemplate.Resources>
            <Style TargetType="{x:Type tk:DataGridCell}">
                <Setter Property="Background" Value="LightGreen" />
            </Style>
        </ControlTemplate.Resources>
        <Grid>
            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </ControlTemplate>
    

    And... No background will be displayed (stays transparent)

    So I think I am working in the wrong way here and I was wondering what should I do to JUST define the "not selected" template. I would say that I may need to define a style BasedOn the "classic" style but, how would I do that? I tried to add TemplateBindings with no success

    ** EDIT: Solution**

    As H B suggested in his answer, problem was coming from DependencyProperty Precedence, here's the solution:

    <!-- Light green template for sophis-related -->
    <ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
        <Grid>
            <Grid.Resources>
                <Style TargetType="{x:Type Grid}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}}, 
                                                Converter={StaticResource DebugConverter}}" Value="True">
                            <Setter Property="Grid.Background" Value="#FF316AC5" />
                        </DataTrigger>
                        <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}}, 
                                                Converter={StaticResource DebugConverter}}" Value="False">
                            <Setter Property="Grid.Background" Value="LightGreen" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Grid.Resources>
            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </ControlTemplate>
    

    Second question

    Now, let's speak Triggers.

    Basically, what I want to do is to define specific Triggers to my ElementStyle so the font color is white if the cell's background is Red or Green (the only aim of this is to have a better readability as Red and Green are kinda dark, black font on dark background results in a nice fail :p )

    Edit Seems like I'm not clear enough: the following style is the style applied to each item of the datagrid, through the property DataGridTextColumn.ElementStyle. Here is the code handling that:

        void VolatilityDataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            DataGridTextColumn column = e.Column as DataGridTextColumn;
            column.ElementStyle = s_boldCellStyle;
            // Other stuff here...
        }
    

    Here is what I do:

    <!-- Cell style for colored matrix-->
    <Style x:Key="BoldCellStyle" TargetType="{x:Type TextBlock}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}}}" 
                         Value="Red">
                <Setter Property="Foreground" Value="White" />
            </DataTrigger>
            <DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}}, 
                                    Converter={StaticResource DebugConverter}}"
                         Value="Green">
                <Setter Property="Foreground" Value="White" />
            </DataTrigger>
        </Style.Triggers>
        <Setter Property="FontWeight" Value="Bold"/>
    </Style>
    

    And... It doesn't work. Strangely, what goes through converter is ONLY transparent background colors. I am definitely missing something here! BTW, I also tried with classic triggers, no success either, I use DataTriggers here so I can debug the binding values!

    Now I've been stuck for more than three days on this and I'm starting to freak out... Hopefully the Stackoverflow community will save me :)

    Thanks!

    Edit

    Okay, update. I understood why my Trigger does not work. The Background actually set is on the Grid and NOT on the DataGridCell. It is therefore normal that I don't get any color set there.

    However, I ran some tests and found out that when the binding is set, the TextBlock does not have any parent yet (Parent = null). Binding to a RelativeSource of type Grid will bind me to... The whole DataGrid items presenter. I'm not sure what to do now, since it seems like that from the actual TextBlock style I can't reach the parent Grid and therefore cannot resolve what color should I display according to the background. Also, I can't change the Font color in my ControlTemplate because the DataGrid wants a Style for each column, which overrides the template's style by default (see my previous question and its answer) So... Stuck again I am!

  • Damascus
    Damascus over 12 years
    Thank you for the useful link! First problem solved thanks to you :) About the second, it's a style defined for TextBlocks, so should I really work with TextElement.Foreground ? I am editing the question because I found out what possibly is the cause -- which may pose a new problem even trickier :/
  • H.B.
    H.B. over 12 years
    @Damascus: The fact that at first there is no parent should not be that problematic. I still do not get where you use that style and how it interacts with the other components though, your question needs more context about that.
  • Damascus
    Damascus over 12 years
    Woops, sorry, just updated the question, the style is used as a DataGridTextColumn.ElementStyle, check this out :)
  • Damascus
    Damascus over 12 years
    Got it! You're totally right, externalizing was the solution. Thank you very much!