当达到100%时,如何停止WPF ProgressBar脉冲/动画?

Tom*_*son 27 wpf mvvm progress-bar

我有一个基于MVVM的WPF 4应用程序,它使用ProgressBar来显示长时间运行的操作的完成百分比.

<ProgressBar Name="ProgressBar"
    IsIndeterminate="False"
    Minimum="0"
    Maximum="100"
    Value="{Binding Path=ProgressPercentageComplete, Mode=OneWay}"
    Visibility="Visible"/>
Run Code Online (Sandbox Code Playgroud)

我很高兴在进度条移动时发生"脉冲"动画,但一旦达到100%,我希望它停止动画并保持静态100%.

我尝试过设置,IsIndeterminate="False"但这没有用,我在阅读MSDN文档后可以看到原因:

当此属性为true时,ProgressBar会以连续的方式设置几个在ProgressBar中移动的条形图并忽略Value属性.

是否可以停止此动画?完全,或只是100%.

nmc*_*ean 10

我使用附加属性为此编写了一个通用解决方案,允许我ProgressBar通过直接属性或样式设置器切换任何行为,如下所示:

<ProgressBar helpers:ProgressBarHelper.StopAnimationOnCompletion="True" />
Run Code Online (Sandbox Code Playgroud)

代码:

public static class ProgressBarHelper {
    public static readonly DependencyProperty StopAnimationOnCompletionProperty =
        DependencyProperty.RegisterAttached("StopAnimationOnCompletion", typeof(bool), typeof(ProgressBarHelper),
                                            new PropertyMetadata(OnStopAnimationOnCompletionChanged));

    public static bool GetStopAnimationOnCompletion(ProgressBar progressBar) {
        return (bool)progressBar.GetValue(StopAnimationOnCompletionProperty);
    }

    public static void SetStopAnimationOnCompletion(ProgressBar progressBar, bool value) {
        progressBar.SetValue(StopAnimationOnCompletionProperty, value);
    }

    private static void OnStopAnimationOnCompletionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
        var progressBar = obj as ProgressBar;
        if (progressBar == null) return;

        var stopAnimationOnCompletion = (bool)e.NewValue;

        if (stopAnimationOnCompletion) {
            progressBar.Loaded += StopAnimationOnCompletion_Loaded;
            progressBar.ValueChanged += StopAnimationOnCompletion_ValueChanged;
        } else {
            progressBar.Loaded -= StopAnimationOnCompletion_Loaded;
            progressBar.ValueChanged -= StopAnimationOnCompletion_ValueChanged;
        }

        if (progressBar.IsLoaded) {
            ReevaluateAnimationVisibility(progressBar);
        }
    }

    private static void StopAnimationOnCompletion_Loaded(object sender, RoutedEventArgs e) {
        ReevaluateAnimationVisibility((ProgressBar)sender);
    }

    private static void StopAnimationOnCompletion_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
        var progressBar = (ProgressBar)sender;

        if (e.NewValue == progressBar.Maximum || e.OldValue == progressBar.Maximum) {
            ReevaluateAnimationVisibility(progressBar);
        }
    }

    private static void ReevaluateAnimationVisibility(ProgressBar progressBar) {
        if (GetStopAnimationOnCompletion(progressBar)) {
            var animationElement = GetAnimationElement(progressBar);
            if (animationElement != null) {
                if (progressBar.Value == progressBar.Maximum) {
                    animationElement.SetCurrentValue(UIElement.VisibilityProperty, Visibility.Collapsed);
                } else {
                    animationElement.InvalidateProperty(UIElement.VisibilityProperty);
                }
            }
        }
    }

    private static DependencyObject GetAnimationElement(ProgressBar progressBar) {
        var template = progressBar.Template;
        if (template == null) return null;

        return template.FindName("PART_GlowRect", progressBar) as DependencyObject;
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上,它添加了一个ValueChanged调整动画元素可见性的处理程序.

几点说明:

  • 我正在"PART_GlowRect"寻找动画元素,虽然有人称这是一个黑客.我不同意:这个元素名称是通过TemplatePartAttribute正式记录的,您可以在ProgressBar的声明中看到它.虽然这确实不一定能保证命名元素的存在,但它应该缺少的唯一原因是完全不支持动画功能.如果它受支持但使用的元素名称与记录的元素名称不同,我会认为是一个错误,而不是实现细节.

  • 由于我从模板中提取元素,因此Loaded在尝试设置初始可见性之前,还需要处理事件(在应用模板时引发)以等待模板可用,并且如果需要,再次设置它当模板被主题更改动态替换时.

  • 我没有明确地VisibilityCollapsed和之间切换,而是Visible使用SetCurrentValue设置为Collapsed,而使用InvalidateProperty来重置它.SetCurrentValue应用不优先于其他值源的值,并InvalidateProperty在不考虑该SetCurrentValue设置的情况下重新评估该属性.这确保了如果现有样式或触发器会影响正常条件下的可见性(即,当它不是 100%时),如果重复使用进度条(从100%返回到0%),它将重置为该行为而不是硬编码Visible.


Dab*_*rnl 8

您可以通过复制整个做到这一点ControlTemplateProgressBar,然后添加一个Trigger为条件在那里ProgressBar.Value=100.原样的XAML将使其ProgressBar行为与现在一样.删除底部的注释标签,当ProgressBar"值"属性达到100 时,动画将停止.唯一的缺点是,当您更改"最大属性"时,ProgressBar您还需要更改"触发器".任何人都知道如何将Trigger绑定到Maximum Property的实际值?

<Window x:Class="ProgressBarSpike.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        Loaded="Window_Loaded">
    <Window.Resources>
        <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="ProgressBarStyleStopAnimation" 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">
                            <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>
                            <!--
                            <Trigger Property="Value" Value="100">
                                <Setter Property="Visibility" TargetName="Animation" Value="Collapsed"/>
                            </Trigger>
                            -->

                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <StackPanel>
        <ProgressBar Name="Progress"  Height="50" Style="{DynamicResource ProgressBarStyleStopAnimation}"/>
    </StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)


小智 8

Dabblernl的答案很强大.这是一个hack(因为它依赖于执行发光的元素的内部名称,这是一个实现细节,可能在后续版本中更改):

void SetGlowVisibility(ProgressBar progressBar, Visibility visibility) {
    var anim = progressBar.Template.FindName("Animation", progressBar) as FrameworkElement;
    if (anim != null)
        anim.Visibility = visibility;
}
Run Code Online (Sandbox Code Playgroud)

如果a ProgressBar实现如何更改,这个hack可能会停止工作.

另一方面,完全取代XAML和样式的解决方案可以锁定并修复颜色,边框等,并禁用将来可能添加到更新版本的ProgressBar的行为...

编辑:实现细节确实发生了变化.改"PART_GlowRect""Animation"-前者只用在aero.normalcolor.xaml,而后者则是在最近的使用aero2.normalcolor.xamlaerolite.normalcolor.xaml太.