【问题标题】:Progress bar with dynamic text & text color update带有动态文本和文本颜色更新的进度条
【发布时间】:2011-07-07 06:35:30
【问题描述】:

我有一个进度条,其文本会动态变化。我想更新它的外观,以便一旦文本出现进度,文本颜色就会更新。像这样的东西。

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

【问题讨论】:

  • 老实说,我不觉得你的想法真的很漂亮。但是一个想法:在进度条上用白色写相同的文本并裁剪覆盖部分..l.
  • 我附上了我的应用程序中唯一进度条的快照。我无法按照您的建议更改进度条的行为。当您看到这个完整的应用程序时,除了文本颜色不可更改之外,这似乎工作得很好..
  • 虽然我添加了看起来效果不错的白色阴影效果,但我正在寻找一个不错的解决方案。这样文本颜色会随着进度条的完成而变化。

标签: wpf progress-bar


【解决方案1】:

这是使用ProgressBars 默认Template 的修改版本的一种方法。它包含两个TextBlocks

  • 第一个TextBlock是黑色的
  • 第二个TextBlock 是白色的。此TextBlock 具有完全控制的宽度,Clip 设置为进度部分的宽度

ProgressBar 的文本绑定到Tag 属性。可以这样使用。

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

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>

矩形转换器

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();
    }
}

【讨论】:

  • +1 不错的仅限 XAML 的解决方案(显然,尽管有值转换器)! :)
  • 嗯,我只是倾向于将价值转换器视为开展 WPF 业务的成本。至少它们非常整齐地封装了特定的逻辑位,如果你编写/组织得好的话......
  • @djacobson:当然,值转换器是 WPF 的重要组成部分。但是当他们实际转换某些东西而不只是双倍转换时,我更喜欢它们。但由于几何类在所有地方都使用结构,因此需要 Rect 转换器
【解决方案2】:

这是 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();
        }
    }
}

看起来像这样:

这是一个快速而肮脏的解决方案,但我认为这是一个开始。希望这会有所帮助。

【讨论】:

    【解决方案3】:

    虽然我使用了 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();
        }
    }
    

    【讨论】:

    • 这里的整个概念是我使用 2 个文本框来显示相同​​的文本:一个具有白色前景,另一个具有黑色前景。文本框和进度条的宽度相同。现在我将具有白色前景的文本框剪辑为等于进度条填充宽度的宽度,以便白色文本块仅在出现进度条填充的部分可见,其余文本为黑色。
    • 我发现了这个问题并将这个答案作为我的答案。我不需要通过控件模板重建进度条,因为此时,应用程序中只有它的位置。我想要一些简单明了的东西,这就是给我的。如果我们需要在多个地方使用它,那么我很可能会将它放入 DataTemplate 中。
    猜你喜欢
    • 2013-02-05
    • 2012-12-01
    • 1970-01-01
    • 2021-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多