我有一个基于MVVM的WPF 4应用程序,它使用ProgressBar来显示长时间运行的完成百分比。
<ProgressBar Name="ProgressBar"
IsIndeterminate="False"
Minimum="0"
Maximum="100"
Value="{Binding Path=ProgressPercentageComplete, Mode=OneWay}"
Visibility="Visible"/>
我很高兴在进度条移动时发生“脉冲”动画,但一旦达到100%,我就希望它能够停止动画并保持100%的静态。
我已尝试设置IsIndeterminate="False"
,但这没有用,我在阅读MSDN文档后可以看到原因:
当此属性为true时, ProgressBar动画了几个小节 跨越ProgressBar连续 方式并忽略Value属性。
是否可以停止此动画?完全,或只是100%。
答案 0 :(得分:9)
我使用附加属性为此编写了一个通用解决方案,允许我通过直接属性或样式设置器切换任何ProgressBar
上的行为,如下所示:
<ProgressBar helpers:ProgressBarHelper.StopAnimationOnCompletion="True" />
代码:
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;
}
}
基本上,它添加了一个ValueChanged
处理程序,用于调整动画元素的可见性。
一些注意事项:
我正在使用"PART_GlowRect"
来查找动画元素,尽管有人称这是一个黑客攻击。我不同意:此元素名称通过TemplatePartAttribute正式记录,您可以在ProgressBar's declaration中看到。虽然这确实不一定能保证命名元素的存在,但它应该缺少的唯一原因是动画特性根本不受支持。如果它受支持但使用的元素名称与记录的元素名称不同,我会认为这是一个错误,而不是实现细节。
由于我从模板中提取元素,因此还需要处理Loaded
事件(在应用模板时引发)以等待模板在尝试之前可用设置初始可见性,并在必要时通过主题更改动态替换模板时再次设置它。
我没有在Visibility
和Collapsed
之间明确切换Visible
,而是使用SetCurrentValue设置为Collapsed
,{{3重置它。 SetCurrentValue
应用的值不会优先于其他值来源,InvalidateProperty
会在不考虑SetCurrentValue
设置的情况下重新评估该属性。这确保了如果现有样式或触发器会影响正常条件下的可见性(即,当它不为100%时),如果重复使用进度条,它将重置为该行为(进行中)从100%回到0%)而不是硬编码到Visible
。
答案 1 :(得分:8)
Dabblernl的答案非常强大。 这是一个hack(因为它依赖于执行发光的元素的内部名称,这是一个实现细节,可能会在后续版本中更改):
void SetGlowVisibility(ProgressBar progressBar, Visibility visibility) {
var anim = progressBar.Template.FindName("Animation", progressBar) as FrameworkElement;
if (anim != null)
anim.Visibility = visibility;
}
如果实施ProgressBar
的方式发生了变化,这个黑客可能会停止工作。
另一方面,完全取代XAML和样式的解决方案可以锁定并修复颜色,边框等,并禁用将来可能添加到更新版本的ProgressBar的行为......
修改:实施细节确实发生了变化。已将"PART_GlowRect"
更改为"Animation"
- 前者仅在aero.normalcolor.xaml
中使用,后者则在最近的aero2.normalcolor.xaml
和aerolite.normalcolor.xaml
中使用。
答案 2 :(得分:7)
您可以通过复制ControlTemplate
的整个ProgressBar
来完成此操作,然后为Trigger
的条件添加ProgressBar.Value=100
。原样的XAML将使ProgressBar
表现得像现在一样。删除底部的注释标签,当ProgressBar
的Value属性达到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>
答案 3 :(得分:1)
您可以交换PART_Indicator
使用的转换器,默认情况下是ProgressBarBrushConverter
,这是动画的来源......
...
TranslateTransform transform = new TranslateTransform();
double num11 = num8 * 100;
DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();
animation.Duration = new Duration(TimeSpan.FromMilliseconds(num11));
animation.RepeatBehavior = RepeatBehavior.Forever;
for (int i = 1; i <= num8; i++)
{
double num13 = i * num7;
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame(num13, KeyTime.Uniform));
}
transform.BeginAnimation(TranslateTransform.XProperty, animation);
...
然后可以修改ProgressBarBrushConverter
的{{3}}以满足您的需求。
您可能必须最终将参数传递给转换器,以便它可以检查值并根据ProgressBar
的状态提供适当的动画或缺少动画。
答案 4 :(得分:0)
我认为唯一的方法是为ProgressBar
创建自定义样式。
IsIndeterminate 的MSDN描述是指另一种行为,默认情况下为false,因此设置它不会改变任何内容。