wpf textblock foreground two color

HoneyBee 186 Reputation points
2022-02-03T14:58:49.063+00:00

There are numbers inside the TextBlock.

I'm making a control that does a countdown.

Text is from 5 to 0
It changes by decreasing by 1.

I want to change the color of this text.

The color will be divided into 5 levels.
The smaller the number, the fuller the color will be.

I used LinearGradientBrush but
I want to create a color combination with a sharp border between two colors.
Can the two color values ​​be applied differently above and below without offset?
171016-image.png

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,696 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,306 Reputation points
    2022-02-03T18:15:07.383+00:00

    Try following code with LinearGradientBrush:

    XAML:

    <Window x:Class="Window110"  
            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:WpfApp1.WpfApp110"  
            mc:Ignorable="d"  
            Title="Window110" Height="300" Width="200" Loaded="Window_Loaded">  
      <Window.Resources>  
        <local:ValueConverter x:Key="conv1"/>  
        <local:ColorConverter x:Key="conv2"/>  
      </Window.Resources>  
        
      <Grid>  
        <TextBlock Text="{Binding Value, ElementName=sl, Converter={StaticResource conv1}}"   
                   Foreground="{Binding Value, ElementName=sl, Converter={StaticResource conv2}}"   
                   FontSize="200" FontWeight="ExtraBlack"  
                   Margin="35 0 30 30" />  
        <Slider x:Name="sl" Minimum="0" Maximum="5" Height="20" VerticalAlignment="Bottom"/>  
      </Grid>  
    </Window>  
    

    Code:

    Imports System.Globalization  
      
    Public Class Window110  
      
      Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)  
        Me.DataContext = Me  
      End Sub  
      
    End Class  
      
    Namespace WpfApp110  
      Public Class ValueConverter  
        Implements IValueConverter  
      
        Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert  
          Dim x = CType(value.ToString, Integer)  
          Return x.ToString  
        End Function  
      
        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack  
          Throw New NotImplementedException()  
        End Function  
      End Class  
      Public Class ColorConverter  
        Implements IValueConverter  
      
        Public Sub New()  
          b.StartPoint = New Point(0.5, 1)  
          b.EndPoint = New Point(0.5, 0)  
          b.GradientStops.Add(stop1)  
          b.GradientStops.Add(stop2)  
          b.GradientStops.Add(stop3)  
          b.GradientStops.Add(stop4)  
        End Sub  
      
        Private b As New LinearGradientBrush()  
        Private stop1 As New GradientStop(Colors.Blue, 0)  
        Private stop2 As New GradientStop(Colors.Blue, 0.001)  
        Private stop3 As New GradientStop(Colors.Yellow, 0.001)  
        Private stop4 As New GradientStop(Colors.Yellow, 1)  
      
      
        Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert  
          Dim x = CType(value.ToString, Double) / 5  
          stop2.Offset = x  
          stop3.Offset = x + 0.001  
          Return b  
        End Function  
      
        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack  
          Throw New NotImplementedException()  
        End Function  
      End Class  
      
    End Namespace  
    

    Result:

    171029-x.gif


1 additional answer

Sort by: Most helpful
  1. Michael Taylor 50,106 Reputation points
    2022-02-03T16:15:51.08+00:00

    I don't think that is possible using traditional effects. The issue is that you're drawing a single character from a font and you specify the color of the font. A gradient color with a single stop would still merge the 2 colors as they approached each other. There doesn't appear to be any properties that would alter that.

    I have a couple of thoughts on how you might be able to do what you want. The first is to create your own brush type that draws the way you want. Effectively it is a gradient brush but doesn't merge the values together. There is a post on Stackoverflow basically asking for the same thing and the recommendation was to use TextureBrush.

    The alternative would be to draw the value twice, overlapping with clipping. The first value is the original color (white in your case) and is fully shown. The second value is the new color (purple) and is completely hidden. As you move the progress up then adjust the first value to clip more of it while the second value is shown more. The net effect is you're slowly replacing the original value with the new value. Of course if you were doing this in a lot of places it could be a perf issue but for something like a single indicator it would be fine.

    You could also probably use a regular progress bar that already fills the way you want and then clip it to bounds of the text using a graph but I don't know how hard that might be.

    0 comments No comments