进度条与动态文本及文本颜色更新

30

我有一个进度条,其文本会动态更改。我希望更新它的外观,以便当进度条覆盖文本时,文本颜色应更新。就像这样。enter image description here

我需要文本颜色 (黑色) 出现在蓝色背景上时自动变为白色。然而,具有白色背景的文本应保持黑色。


1
老实说,我觉得你的想法并不是特别美。但有一个想法:在进度条上用白色写相同的文本,然后裁剪重叠的部分... - fixagon
我已经附上了我的应用程序中进度条的快照。我无法像你建议的那样更改进度条的行为。当您在整个应用程序中查看时,除了文本颜色不可更改外,它似乎运行良好。 - Rohit
虽然我已经添加了白色阴影效果,看起来效果不错,但我正在寻找一个更好的解决方案。这样文本颜色会随着进度条的完成而改变。 - Rohit
3个回答

49

以下是一种使用经过修改的 ProgressBars 默认 Template 的方法。它包含两个 TextBlocks

  • 第一个 TextBlock 是黑色的
  • 第二个 TextBlock 是白色的。这个 TextBlock 宽度与整个控件相同,并且 Clip 设置为进度部分的宽度

enter image description here

< p > ProgressBar 的文本是绑定到 Tag 属性的。可以像这样使用。

<ProgressBar TextBlock.FontWeight="Bold"
             Tag="ProgressBar Text"
             Foreground="Blue"
             Style="{DynamicResource MyProgressBarStyle}"/>

我的进度条样式

<LinearGradientBrush x:Key="ProgressBarBackground" EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#BABABA" Offset="0"/>
    <GradientStop Color="#C7C7C7" Offset="0.5"/>
    <GradientStop Color="#BABABA" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ProgressBarBorderBrush" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#B2B2B2" Offset="0"/>
    <GradientStop Color="#8C8C8C" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ProgressBarGlassyHighlight" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#50FFFFFF" Offset="0.5385"/>
    <GradientStop Color="#00FFFFFF" Offset="0.5385"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ProgressBarTopHighlight" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#80FFFFFF" Offset="0.05"/>
    <GradientStop Color="#00FFFFFF" Offset="0.25"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00FFFFFF" Offset="0"/>
    <GradientStop Color="#60FFFFFF" Offset="0.4"/>
    <GradientStop Color="#60FFFFFF" Offset="0.6"/>
    <GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ProgressBarIndicatorDarkEdgeLeft" EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#0C000000" Offset="0"/>
    <GradientStop Color="#20000000" Offset="0.3"/>
    <GradientStop Color="#00000000" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ProgressBarIndicatorDarkEdgeRight" EndPoint="1,0" StartPoint="0,0">
    <GradientStop Color="#00000000" Offset="0"/>
    <GradientStop Color="#20000000" Offset="0.7"/>
    <GradientStop Color="#0C000000" Offset="1"/>
</LinearGradientBrush>
<RadialGradientBrush x:Key="ProgressBarIndicatorLightingEffectLeft" RadiusY="1" RadiusX="1" RelativeTransform="1,0,0,1,0.5,0.5">
    <GradientStop Color="#60FFFFC4" Offset="0"/>
    <GradientStop Color="#00FFFFC4" Offset="1"/>
</RadialGradientBrush>
<LinearGradientBrush x:Key="ProgressBarIndicatorLightingEffect" EndPoint="0,0" StartPoint="0,1">
    <GradientStop Color="#60FFFFC4" Offset="0"/>
    <GradientStop Color="#00FFFFC4" Offset="1"/>
</LinearGradientBrush>
<RadialGradientBrush x:Key="ProgressBarIndicatorLightingEffectRight" RadiusY="1" RadiusX="1" RelativeTransform="1,0,0,1,-0.5,0.5">
    <GradientStop Color="#60FFFFC4" Offset="0"/>
    <GradientStop Color="#00FFFFC4" Offset="1"/>
</RadialGradientBrush>
<LinearGradientBrush x:Key="ProgressBarIndicatorGlassyHighlight" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#90FFFFFF" Offset="0.5385"/>
    <GradientStop Color="#00FFFFFF" Offset="0.5385"/>
</LinearGradientBrush>
<Style x:Key="MyProgressBarStyle" TargetType="{x:Type ProgressBar}">
    <Setter Property="Foreground" Value="#01D328"/>
    <Setter Property="Background" Value="{StaticResource ProgressBarBackground}"/>
    <Setter Property="BorderBrush" Value="{StaticResource ProgressBarBorderBrush}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ProgressBar}">
                <Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
                    <TextBlock Text="{TemplateBinding Tag}" Grid.ZIndex="2" Foreground="Black"
                                        HorizontalAlignment="Center"
                                        VerticalAlignment="Center"/>
                    <TextBlock Text="{TemplateBinding Tag}"
                                Grid.ZIndex="3" Foreground="White"
                                Width="{Binding ElementName=rectangle, Path=ActualWidth}"
                                TextAlignment="Center"
                                HorizontalAlignment="Stretch"
                                VerticalAlignment="Center">
                        <TextBlock.Clip>
                            <RectangleGeometry>
                                <RectangleGeometry.Rect>
                                    <MultiBinding Converter="{StaticResource RectConverter}">
                                        <Binding ElementName="Indicator" Path="ActualWidth"/>
                                        <Binding ElementName="Indicator" Path="ActualHeight"/>
                                    </MultiBinding>
                                </RectangleGeometry.Rect>
                            </RectangleGeometry>
                        </TextBlock.Clip>
                    </TextBlock>
                    <Rectangle x:Name="rectangle" Fill="{TemplateBinding Background}" RadiusY="2" RadiusX="2"/>
                    <Border Background="{StaticResource ProgressBarGlassyHighlight}" CornerRadius="2" Margin="1"/>
                    <Border BorderBrush="#80FFFFFF" BorderThickness="1,0,1,1" Background="{StaticResource ProgressBarTopHighlight}" Margin="1"/>
                    <Rectangle x:Name="PART_Track" Margin="1"/>
                    <Decorator x:Name="PART_Indicator" HorizontalAlignment="Left" Margin="1">
                        <Grid x:Name="Foreground">
                            <Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}"/>
                            <Grid x:Name="Animation" ClipToBounds="true">
                                <Rectangle x:Name="PART_GlowRect" Fill="{StaticResource ProgressBarIndicatorAnimatedFill}" HorizontalAlignment="Left" Margin="-100,0,0,0" Width="100"/>
                            </Grid>
                            <Grid x:Name="Overlay">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition MaxWidth="15"/>
                                    <ColumnDefinition Width="0.1*"/>
                                    <ColumnDefinition MaxWidth="15"/>
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>
                                <Rectangle x:Name="LeftDark" Fill="{StaticResource ProgressBarIndicatorDarkEdgeLeft}" Margin="1,1,0,1" RadiusY="1" RadiusX="1" Grid.RowSpan="2"/>
                                <Rectangle x:Name="RightDark" Grid.Column="2" Fill="{StaticResource ProgressBarIndicatorDarkEdgeRight}" Margin="0,1,1,1" RadiusY="1" RadiusX="1" Grid.RowSpan="2"/>
                                <Rectangle x:Name="LeftLight" Grid.Column="0" Fill="{StaticResource ProgressBarIndicatorLightingEffectLeft}" Grid.Row="2"/>
                                <Rectangle x:Name="CenterLight" Grid.Column="1" Fill="{StaticResource ProgressBarIndicatorLightingEffect}" Grid.Row="2"/>
                                <Rectangle x:Name="RightLight" Grid.Column="2" Fill="{StaticResource ProgressBarIndicatorLightingEffectRight}" Grid.Row="2"/>
                                <Border x:Name="Highlight1" Background="{StaticResource ProgressBarIndicatorGlassyHighlight}" Grid.ColumnSpan="3" Grid.RowSpan="2"/>
                                <Border x:Name="Highlight2" Background="{StaticResource ProgressBarTopHighlight}" Grid.ColumnSpan="3" Grid.RowSpan="2"/>
                            </Grid>
                        </Grid>
                    </Decorator>
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="Orientation" Value="Vertical">
                        <Setter Property="LayoutTransform" TargetName="TemplateRoot">
                            <Setter.Value>
                                <RotateTransform Angle="-90"/>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                    <Trigger Property="IsIndeterminate" Value="true">
                        <Setter Property="Visibility" TargetName="LeftDark" Value="Collapsed"/>
                        <Setter Property="Visibility" TargetName="RightDark" Value="Collapsed"/>
                        <Setter Property="Visibility" TargetName="LeftLight" Value="Collapsed"/>
                        <Setter Property="Visibility" TargetName="CenterLight" Value="Collapsed"/>
                        <Setter Property="Visibility" TargetName="RightLight" Value="Collapsed"/>
                        <Setter Property="Visibility" TargetName="Indicator" Value="Collapsed"/>
                    </Trigger>
                    <Trigger Property="IsIndeterminate" Value="false">
                        <Setter Property="Background" TargetName="Animation" Value="#80B5FFA9"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

RectConverter

public class RectConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        double width = (double)values[0];
        double height = (double)values[1];
        return new Rect(0, 0, width, height);
    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

5
+1 不错的 XAML-only 解决方案(当然,值转换器除外)! :) - Dan J
嗯,我倾向于将值转换器视为使用 WPF 的成本。至少,如果你编写/组织得当,它们可以很好地封装特定的逻辑部分。 - Dan J
当然,值转换器是WPF的重要组成部分。但是我更喜欢它们在实际转换某些内容而不仅仅是double类型时的表现。但由于Geometry类在很多地方都使用了结构体,所以需要Rect转换器。 - Fredrik Hedblad

7

这里提供了一个Silverlight的解决方案,但是将其转换为WPF应该很容易。

我使用线性渐变刷来改变文本块中的文本颜色,我创建了一个带有进度条和文本块的用户控件,我们称之为“SpecialProgressBar”

以下是XAML代码:

<UserControl x:Class="TestSilverlightApplication.SpecialProgressBar"
             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"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="400"
             x:Name="specialProgressBar">

    <Canvas  Width="Auto"
             Height="Auto">

        <ProgressBar Name="progressBar"
                     IsIndeterminate="False"
                     Background="White"
                     Foreground="Blue"
                     Height="{Binding Height, ElementName=specialProgressBar}"
                     Width="{Binding Width, ElementName=specialProgressBar}" />

        <TextBlock x:Name="textBlock"
                   FontWeight="Bold"
                   Text="xxx of yyy" />
    </Canvas>
</UserControl>

以下是代码:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace TestSilverlightApplication
{
    public partial class SpecialProgressBar : UserControl
    {
        private Point _textBlockPosition;
        private readonly LinearGradientBrush _linearGradientBrush;
        private readonly GradientStop _gradientStop;

        public SpecialProgressBar()
        {
            InitializeComponent();

            // will be changing this gradient stop as the progress bar value changes
            _gradientStop = new GradientStop
            {
                Color = Colors.Black,
                Offset = 0
            };

            // the default brush we want to start with,
            // you might want to play with the start point x value to get the effect you want
            _linearGradientBrush = new LinearGradientBrush
            {
                StartPoint = new Point(-0.2, 0.5),
                EndPoint = new Point(1, 0.5),
                GradientStops = new GradientStopCollection
                {
                    _gradientStop,
                    new GradientStop
                    {
                        Color = Colors.Black,
                        Offset = 1
                    }
                }
            };

            // set the brush to the text block 
            textBlock.Foreground = _linearGradientBrush;

            Loaded += new RoutedEventHandler(SpecialProgressBar_Loaded);
            progressBar.ValueChanged += new RoutedPropertyChangedEventHandler<double>(progressBar_ValueChanged);
        }

        private void SpecialProgressBar_Loaded(object sender, RoutedEventArgs e)
        {
            // center text block on top of the progress bar
            _textBlockPosition = new Point(progressBar.Width / 2 - textBlock.ActualWidth / 2,
                                           progressBar.Height / 2 - textBlock.ActualHeight / 2);

            textBlock.SetValue(Canvas.LeftProperty, _textBlockPosition.X);
            textBlock.SetValue(Canvas.TopProperty, _textBlockPosition.Y);
        }

        private void progressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            // print out the value in the text block
            textBlock.Text = string.Concat(e.NewValue, " of ", progressBar.Maximum);

            // get the value relative to the size of the progress bar
            var x = e.NewValue / progressBar.Maximum * progressBar.Width;             

            // if the value is equal to or greater than the position of the text block on the canvas (on the progress bar)
            // then we want to change the gradient offset and color.
            if (x >= _textBlockPosition.X)
            {
                _gradientStop.Offset += 0.1 * textBlock.ActualWidth / progressBar.Width;
                _gradientStop.Color = Colors.White;

                // when we pass the end of the text block we don't need the gradient any more,
                // replace it with a solid white color
                if (_gradientStop.Offset >= 1)
                {
                    textBlock.Foreground = new SolidColorBrush(Colors.White);
                }
            }
        }
    }
}

最后一步是将用户控件添加到页面(或另一个用户控件)中。
<UserControl x:Class="TestSilverlightApplication.MainPage"
             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:TestSilverlightApplication="clr-namespace:TestSilverlightApplication"
             mc:Ignorable="d">

    <Grid>
        <TestSilverlightApplication:SpecialProgressBar x:Name="specialProgressBar"
                                                       Width="200"
                                                       Height="40" />
    </Grid>
</UserControl>

为了测试它,我添加了一个定时器来改变进度条的值:

using System;
using System.Windows.Controls;
using System.Windows.Threading;

namespace TestSilverlightApplication
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

            this.Loaded += new System.Windows.RoutedEventHandler(MainPage_Loaded);
        }

        private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            var timer = new DispatcherTimer();
            timer.Tick += (s, args) => specialProgressBar.progressBar.Value += 1;
            timer.Interval = new TimeSpan(1000000);
            timer.Start();
        }
    }
}

看起来是这样的:

enter image description here

虽然这只是一个简单粗糙的解决方案,但我认为它是一个好的开始。希望这有所帮助。


6

虽然我没有使用Meleak提供的整个解决方案,但这是我是如何做到的。

<ProgressBar x:Name="SummaryProgressBar"
                         BorderBrush="Black"
                         BorderThickness="1"
                         Background="LightGray"
                         FlowDirection="LeftToRight"
                         Maximum="1"
                         MinWidth="200"
                         Width="Auto">
                <ProgressBar.Value>
                    <MultiBinding Converter="{StaticResource ArithmeticConverter}"
                                  Mode="OneWay">
                        <Binding Path="ABCCollectionView.Count"/>
                        <Binding Source="{StaticResource DivideArithmeticSymbol}" />
                        <Binding Path="XYZCollectionView.Count"/>
                    </MultiBinding>
                </ProgressBar.Value>
            </ProgressBar>
            <!-- Black Progress Bar Text -->
            <TextBlock x:Name="TextBlockBlack"
                       VerticalAlignment="Center"
                       TextAlignment="Center"
                       HorizontalAlignment="Stretch"
                       FontWeight="Bold"
                       Foreground="Black"
                       Text="{Binding SummaryText}"
                       Width="{Binding ElementName=SummaryProgressBar, Path=ActualWidth}"></TextBlock>

            <!-- White Progress Bar Text -->
            <TextBlock x:Name="TextBlockWhite"
                       VerticalAlignment="Center"
                       TextAlignment="Center"
                       HorizontalAlignment="Stretch"
                       FontWeight="Bold"
                       Foreground="White"
                       Text="{Binding SummaryText}"
                       Width="{Binding ElementName=SummaryProgressBar, Path=ActualWidth}">
                <TextBlock.Clip>
                    <RectangleGeometry>
                        <RectangleGeometry.Rect>
                            <MultiBinding Converter="{StaticResource ProgressBarFillToRectConverter}">
                                    <Binding ElementName="SummaryProgressBar" Path="Value"/>
                                    <Binding ElementName="TextBlockWhite" Path="ActualWidth" />
                                    <Binding ElementName="TextBlockWhite" Path="ActualHeight"/>
                                </MultiBinding>
                        </RectangleGeometry.Rect>
                    </RectangleGeometry>
                </TextBlock.Clip>
            </TextBlock>

这里是转换器

 /// <summary>
/// Converts the ProgressBar Fill percentage width to a Rectangle whose width is calculated by multiplying Fill Percentage to Actual Width of control. Height is passed too.
/// Note: This converter is used in showing WHITE & BLACK text on progress bar. Also use White textblock next to Black not reverse in XAML.
/// </summary>
public class ProgressBarFillToRectConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values != null && values[0] != null && values[1] != null && values[2] != null)
        {
            double progressBarFillPercentage = (double)values[0];
            double textBlockActualyWidth = (double)values[1];
            double textBlockHeight = (double)values[2];
            return new Rect(0, 0, progressBarFillPercentage * textBlockActualyWidth, textBlockHeight); // ProgressBarFillWidth is calculated by multiplying Fill 
            // percentage with actual width
        }
        return new Rect(0, 0, 0, 0); // Default Zero size rectangle

    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

整个概念是我使用了两个文本框来显示相同的文本:一个带有白色前景,另一个带有黑色前景。两个文本框和进度条具有相同的宽度。 现在,我将白色前景的文本框剪裁到与进度条填充宽度相等的宽度,以便白色文本块仅在进度条填充出现的部分可见,其余文本为黑色。 - Rohit
我发现了这个问题,并将这个答案标记为适合我的答案。因为在此时,应用程序中只有一个地方需要重建进度条,所以我不需要通过控件模板来重新构建它。我想要简单明了的东西,而这对我来说就是最好的选择。如果我们需要在多个地方使用它,那么我很可能会将其放入DataTemplate中。 - blandau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接