-1

I have read that for better performance, you should apply opacity to the foreground/background brush rather than the entire element. That is what I am trying to do, but I cannot figure it out.

Here is my XAML that works, but is setting the entire TextBlock element opacity:

<DataGrid>

    <DataGrid.Resources>
        <local:OpacityConverter x:Key="OpacityConverterKey" />
    </DataGrid.Resources>

    <DataGrid.Columns>
        <DataGridTextColumn Width="1*" Binding="{Binding Number}">
            <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Setter Property="Opacity" Value="{Binding Number, Converter={StaticResource OpacityConverterKey}}" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Status}" Value="0">
                            <Setter Property="Foreground" Value="Lime" />
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Status}" Value="1">
                            <Setter Property="Foreground" Value="Red" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>
    </DataGrid.Columns>

</DataGrid>

Here was my attempt to bind the opacity for just the foreground brush of TextBlock:

<DataGrid>

    <DataGrid.Resources>
        <local:OpacityConverter x:Key="OpacityConverterKey" />
    </DataGrid.Resources>

    <DataGrid.Columns>
        <DataGridTextColumn Width="1*" Binding="{Binding Number}">

            <TextBlock.Foreground>
                <SolidColorBrush Color="Blue" Opacity="{Binding Distance, Converter={StaticResource OpacityConverterKey}}" />
            </TextBlock.Foreground>

            <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Status}" Value="0">
                            <Setter Property="Foreground" Value="Lime" />
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Status}" Value="1">
                            <Setter Property="Foreground" Value="Red" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>
    </DataGrid.Columns>

</DataGrid>

The converter does not work in this situation. Visual Studio underlines it and says "No DataContext found for binding." It doesn't have issue with the first example though.

How can I go about just setting the brush opacity?

Thanks for your time.

javaturkey
  • 27
  • 5
  • "*I have read that for better performance ...*" - where did you read that? Even if it would be true, do you have any actual performance problem? What you are trying to do here seems overly complicated. – Clemens Feb 25 '23 at 08:32
  • Besides that, you may write a converter that returns the desired SolidColorBrush instead of an opacity value. Then just write `` – Clemens Feb 25 '23 at 08:40
  • I think you're trying to solve a problem that doesn't really exist. I'm curious as to where the opacity advice came from. – Andy Feb 25 '23 at 08:52
  • You are correct about the Opacity. I remember Microsoft recommends to rather set Opacity on a Brush than on the element. Setting UIElement.Opacity of an element could cause WPF to create a temporary rendering surface. Microsoft only mentions `Shape.Fill` and `Shape.Stroke` explicitly in this context, so it's not clear if this only applies to `Shape` objects or if it applies to all UIElements and Fill and Stroke were used synonymous to Background and Foreground. Maybe you can omit this specific optimization and fall back to it if you experience any rendering issues. – BionicCode Feb 25 '23 at 09:09
  • There are more important optimizations to consider. For example, if you are interested in squeezing out every performance benefit you should also know that defining a Brush inline (what you actually did) will cause the XAML engine to create a new Brush for *every* instance of the element. For example, if you define the Brush inline of a TextBox and this TextBox is part of a DataTemplate that is applied to 1k items, you will end up with 1k instances of the same Brush. Instead you should define the Brush as a resource and reference it preferably using the `StaticResource` markup extension. – BionicCode Feb 25 '23 at 09:09
  • Resources are shared by default. In case of the example all 1k elements would use the same single Brush. – BionicCode Feb 25 '23 at 09:12
  • Just don't put that brush in a resource dictionary. Because you'd likely find it frozen. The efficiency concerns are likely of academic concern. I once worked on airport displays. Ours were client configurable. First version had a storyboard per textblock. I've seen thousands flashing at once. Whilst I recovered I suggested just the one flashing animation so everything flashed in union. – Andy Feb 25 '23 at 11:41
  • Clemens, I read it from here: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/optimizing-performance-other-recommendations – javaturkey Feb 25 '23 at 15:10
  • Is there a pattern to the distance to opacity conversion? If you are concerned about efficiency then you could have an array of 10 or so brushes with different opacities defined by alpha of the colour and pick one out there. You then re-use brushes. However, this is only visible textblocks we're talking in one column. Maybe 50 textblocks? Or is this multiple columns? – Andy Feb 27 '23 at 14:26

1 Answers1

0

Your given example does not work because the Brush is assigned to a property (Foreground). Therefore it is not part of the visual tree (it's a simple property value). Either follow the suggestion of Clemens (return a configured Brush instead of an Opacity value) or animate the Foreground property using a trigger or define the Brush as a Resource using a Style.

When defining the Brush as a resource it will be able to inherit the DataContext of the styled element.

The following example will give you both performance optimizations:
a) modify Brush.Opacity instead of UIElement.Opacity
b) and use a shared resource instead of an inlined object (inlined objects are copied for each element instance).

<Window>
  <Window.Resources>
    <local:OpacityConverter x:Key="OpacityConverter" />

    <Style TargetType="TextBlock">
      <Style.Resources>

        <!-- Resource will inherit the DataContext of TextBlock -->
        <SolidColorBrush x:Key="ForegroundBrush" Color="Red"
                         Opacity="{Binding Distance, Converter={StaticResource OpacityConverter}}" />
      </Style.Resources>

      <Setter Property="Foreground"
              Value="{StaticResource ForegroundBrush}" />
    </Style>
  </Window.Resources>
</Window>

Example that shows how to change the foreground brush's Opacity and Foreground of all cells in a DataGrid:

<DataGrid>
  <DataGrid.CellStyle>
    <Style TargetType="DataGridCell">
      <Style.Resources>
        <local:OpacityConverter x:Key="OpacityConverter" />
        <SolidColorBrush x:Key="ForegroundBrush"
                         Color="Blue"
                         Opacity="{Binding Distance, Converter={StaticResource OpacityConverterKey}}" />
      </Style.Resources>

      <Setter Property="Foreground"
              Value="{StaticResource ForegroundBrush}" />

      <Style.Triggers>
        <DataTrigger Binding="{Binding Status}"
                     Value="0">
          <Setter Property="Foreground"
                  Value="Lime" />
        </DataTrigger>
        <DataTrigger Binding="{Binding Status}"
                     Value="1">
          <Setter Property="Foreground"
                  Value="Red" />
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </DataGrid.CellStyle>      
</DataGrid>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thank you for the explanation, this example does not work for me though. Visual Studio underlines the – javaturkey Feb 25 '23 at 15:08
  • Yes, that was a typo. Regarding the warning, just build and run the application and the squiggles will disappear. – BionicCode Feb 25 '23 at 17:20
  • I've almost got this working correctly, but now my cells textblocks won't inherit this on their own for some reason. I have to give the style a key and then Style="{StaticResource TextBlockStyle}" for each. – javaturkey Feb 25 '23 at 17:35
  • I'm not sure if I understand your current problem correctly. If you want to use an implicit Style (no x:Key) then make sure you have placed the Style in the proper ResourceDictionray that is within the scope of your DataGrid, for example in App.xaml. – BionicCode Feb 25 '23 at 17:39
  • I suspect it would work if I just added a top level TextBlock, but because the TextBlocks are part of TargetType="TextBlock" isnt targeting them. – javaturkey Feb 25 '23 at 20:56
  • Because the column definitions themselves are not rendered (part of the visual tree) the resource lookup behavior is slightly different. Either define the ElementStyle explicitly (inline or StaticResource) or in your case set the DataGrid.CellStyle instead (the Style must target DataGridCell). This way you can target all TextBlock elements. Setting DataGridCell.Foreground of the cell will also impact the TextBlock.Foreground value (it is inherited). – BionicCode Feb 26 '23 at 09:10
  • I have updated the example to show how to manipulate the Foreground of the DataGrid (without doing so explicitly for every column). – BionicCode Feb 26 '23 at 12:13