WPF在模板中平滑地设置ProgressBar动画

本文关键字:设置 ProgressBar 动画 平滑 WPF | 更新日期: 2023-09-27 18:28:07

我正在开发一个MVVM应用程序,我希望有一个ProgressBar,当该属性更改时,它可以平滑地动画化为新值。我已经用c#看到了这个问题的几个答案,但我更喜欢在模板中完成所有这些。我遇到的问题是正确地设置和定位事件和故事板。这是我目前拥有的:

进度条-

风格-(只是触发器)

                    <ControlTemplate.Triggers>
                    <EventTrigger RoutedEvent="RangeBase.ValueChanged">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetName="???????" 
                                    Storyboard.TargetProperty="Value"
                                    To="???????" Duration="0:0:5"  />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>

我从这里获取了触发代码:http://msdn.microsoft.com/en-us/library/system.windows.controls.progressbar(v=vs.110).aspx.

如何将TargetName设置为模板本身,以便它应用于使用此模板的所有控件?如何将"To"设置为传入值?似乎有一种方法可以获取"Binding"值,但我将value和Max都绑定在progressbar元素上。它怎么知道该用什么?

以下是整个模板供参考:

    <Style x:Key="ProgressStyle" TargetType="{x:Type ProgressBar}">
    <Setter Property="OverridesDefaultStyle" Value="True" />
    <Setter Property="SnapsToDevicePixels" Value="True" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ProgressBar}">
                <Grid MinHeight="14" MinWidth="20">
                    <Border x:Name="BaseRectangle" Background="{StaticResource BaseColor}" CornerRadius="10,0,10,0"></Border>
                    <Border x:Name="GlassRectangle" CornerRadius="10,0,10,0"  Background="{StaticResource GlassFX}" Panel.ZIndex="10"></Border>
                    <Border x:Name="animation" CornerRadius="10,0,10,0" Opacity=".7" Background="{Binding Path=Foreground, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left"></Border>
                    <Border x:Name="PART_Indicator" CornerRadius="10,0,10,0" Background="{Binding Path=Foreground, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left"></Border>
                    <Border x:Name="PART_Track" BorderThickness="1" CornerRadius="10,0,10,0" BorderBrush="Black"></Border>
                    <Border x:Name="BordeCabeceraSombra" BorderThickness="2" CornerRadius="10,0,10,0" BorderBrush="DarkGray" Opacity=".2" Margin="1,1,1,0"></Border>
                    <Label x:Name="Progress" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontWeight="Bold" Foreground="White" Opacity=".7" Content="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}}"></Label>
                </Grid>
                <ControlTemplate.Triggers>
                    <EventTrigger RoutedEvent="RangeBase.ValueChanged">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetName="???????" 
                                    Storyboard.TargetProperty="Value"
                                    From="???????" To="???????" Duration="0:0:5"  />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                    <Trigger Property="IsIndeterminate" Value="True">
                        <Setter Property="Visibility" TargetName="Progress" Value="Hidden"></Setter>
                        <Setter Property="Background" TargetName="PART_Indicator">
                            <Setter.Value>
                                <MultiBinding>
                                    <MultiBinding.Converter>
                                        <wintheme:ProgressBarHighlightConverter/>
                                    </MultiBinding.Converter>
                                    <Binding Source="{StaticResource GlowFXProgressAnimated}"/>
                                    <Binding Path="ActualWidth"  ElementName="BaseRectangle"/>
                                    <Binding Path="ActualHeight" ElementName="BaseRectangle"/>
                                </MultiBinding>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Opacity" Value=".5"></Setter>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

任何帮助都将不胜感激!

WPF在模板中平滑地设置ProgressBar动画

我认为这是更好的方法。

您可以创建行为来执行此操作。(MVVM WPF)

创建类:

class ProgresBarAnimateBehavior : Behavior<ProgressBar>
{
    bool _IsAnimating = false;
    protected override void OnAttached()
    {
        base.OnAttached();
        ProgressBar progressBar = this.AssociatedObject;
        progressBar.ValueChanged += ProgressBar_ValueChanged;
    }
    private void ProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        if (_IsAnimating)
            return;
        _IsAnimating = true;
        DoubleAnimation doubleAnimation = new DoubleAnimation
            (e.OldValue, e.NewValue, new Duration(TimeSpan.FromSeconds(0.3)), FillBehavior.Stop);
        doubleAnimation.Completed += Db_Completed;
        ((ProgressBar)sender).BeginAnimation(ProgressBar.ValueProperty, doubleAnimation);
        e.Handled = true;
    }
    private void Db_Completed(object sender, EventArgs e)
    {
        _IsAnimating = false;
    }
    protected override void OnDetaching()
    {
        base.OnDetaching();
        ProgressBar progressBar = this.AssociatedObject;
        progressBar.ValueChanged -= ProgressBar_ValueChanged;
    }
}

简单用法:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:b="clr-namespace:YOURNAMESPACE.Behaviors"
<ProgressBar Height="7"
             Value="{Binding LoadingValue}">
    <i:Interaction.Behaviors>
        <b:ProgresBarAnimateBehavior />
    </i:Interaction.Behaviors>
</ProgressBar>

我从未真正找到解决方案。我最终只是写了自己的控制。从技术上讲,这不是这个问题的答案,但我想我也可以发布它。如果有人正在为MVVM寻找动画进度控制,这可能会有所帮助。

    namespace Card_System.Controls
{
    /// <summary>
    /// Interaction logic for StatProgressBar.xaml
    /// </summary>
    public partial class StatProgressBar : UserControl
    {
        private double _trackWidth;
        private bool _isAnimate;
        private bool _isRefresh;
        public StatProgressBar()
        {
            InitializeComponent();
            var descriptor = DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Border));
            if (descriptor != null)
            {
                descriptor.AddValueChanged(TrackBorder, ActualWidth_ValueChanged);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private double _barValueSet;
        public double BarValueSet
        {
            get { return _barValueSet; }
            set 
            {
                _barValueSet = value;
                OnPropertyChanged("BarValueSet");
                _isAnimate = true;
                AnimateWidth();
            }
        }
        public double BarValueDesired
        {
            get { return (double)GetValue(BarValueProperty); }
            set { SetValue(BarValueProperty, value); }
        }
        public static readonly DependencyProperty BarValueProperty =
          DependencyProperty.Register("BarValueDesired", typeof(double), typeof(StatProgressBar), new UIPropertyMetadata(0.0d, new PropertyChangedCallback(BarValueDesired_PropertyChanged)));
        public double BarMaximum
        {
            get { return (double)GetValue(BarMaximumProperty); }
            set { SetValue(BarMaximumProperty, value); }
        }
        public static readonly DependencyProperty BarMaximumProperty =
          DependencyProperty.Register("BarMaximum", typeof(double), typeof(StatProgressBar), new UIPropertyMetadata(0.0d));

        public static void BarValueDesired_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Set BarValue to the value of BarValueDesired BEFORE it was just changed.
            ((StatProgressBar)d).BarValueSet = (double)e.OldValue;
        }
        public Brush BarColor
        {
            get { return (Brush)GetValue(BarColorProperty); }
            set { SetValue(BarColorProperty, value); }
        }
        public static readonly DependencyProperty BarColorProperty =
          DependencyProperty.Register("BarColor", typeof(Brush), typeof(StatProgressBar), new UIPropertyMetadata(Brushes.White, new PropertyChangedCallback(BarColor_PropertyChanged)));
        public static void BarColor_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((StatProgressBar)d).BarFill.Background = (Brush)e.NewValue;
        }
        private void ActualWidth_ValueChanged(object a_sender, EventArgs a_e)
        {
            _trackWidth = TrackBorder.ActualWidth;
            _isRefresh = true;
            AnimateWidth();
        }
        public void AnimateWidth()
        {
            if (_isAnimate && _isRefresh)
            {
                double StartPoint = new double();
                double EndPoint = new double();
                double PercentEnd = new double();
                double PercentStart = new double();
                PercentStart = BarValueSet / BarMaximum;
                StartPoint = _trackWidth * PercentStart;
                PercentEnd = BarValueDesired / BarMaximum;
                EndPoint = _trackWidth * PercentEnd;
                DoubleAnimation animation = new DoubleAnimation(StartPoint, EndPoint, TimeSpan.FromSeconds(3));
                this.BarFill.BeginAnimation(Border.WidthProperty, animation);
            }
            else return;
        }
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

这是XAML:

<Grid>
<Grid MinHeight="14" MinWidth="20">
    <Border x:Name="BaseRectangle" Background="{StaticResource BaseColor}" CornerRadius="0,0,0,0"/>
    <Border x:Name="TrackBorder" BorderThickness="1" CornerRadius="0,0,0,0" BorderBrush="Black" Panel.ZIndex="20"/>
    <Border x:Name="BarFill" HorizontalAlignment="Left" Opacity=".7" Background="White"/>
    <Border x:Name="GlassOverlay" CornerRadius="0,0,0,0" Background="{StaticResource GlassFX}" Panel.ZIndex="10"/>
    <Border x:Name="GlassOverlayBorder" BorderThickness="4" CornerRadius="0,0,0,0" BorderBrush="DarkGray" Opacity=".2" Panel.ZIndex="12"/>
</Grid>

我知道这个问题已经解决了,但我发现了一个非常好的实现,它不需要创建UserControl。它模仿"理发杆效应",开箱即用:

<SolidColorBrush x:Key="ProgressBarBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarBackgroundBrush" Color="White" />
<SolidColorBrush x:Key="ProgressBarTrackBackgroundBrush" Color="#63D055" />
<Style x:Key="{x:Type ProgressBar}" TargetType="{x:Type ProgressBar}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ProgressBar}">
                <local:ClippingBorder x:Name="BorderBackground" CornerRadius="3" BorderThickness="0"
                        BorderBrush="{StaticResource ProgressBarBorderBrush}"
                        Background="{StaticResource ProgressBarBackgroundBrush}">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Determinate" />
                                <VisualState x:Name="Indeterminate" />
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="PART_Track" Margin="0" BorderThickness="0" CornerRadius="3" />
                        <Border x:Name="PART_Indicator" Margin="0" BorderThickness="0" CornerRadius="3" HorizontalAlignment="Left"
                                Background="{StaticResource ProgressBarTrackBackgroundBrush}" ClipToBounds="True">
                            <Border x:Name="DiagonalDecorator" Width="5000">
                                <Border.Background>
                                    <DrawingBrush TileMode="Tile" Stretch="None" Viewbox="0,0,1,1" Viewport="0,0,36,34" ViewportUnits="Absolute">
                                        <DrawingBrush.RelativeTransform>
                                            <TranslateTransform X="0" Y="0" />
                                        </DrawingBrush.RelativeTransform>
                                        <DrawingBrush.Drawing>
                                            <GeometryDrawing Brush="#48C739" Geometry="M0,0 18,0 36,34 18,34 Z" />
                                        </DrawingBrush.Drawing>
                                    </DrawingBrush>
                                </Border.Background>
                                <Border.Triggers>
                                    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <DoubleAnimation
                                                    Storyboard.TargetProperty="(Border.Background).(DrawingBrush.RelativeTransform).(TranslateTransform.X)"
                                                    From="0" To=".36" RepeatBehavior="Forever" Duration="0:0:18" />
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </EventTrigger>
                                </Border.Triggers>
                            </Border>
                        </Border>
                    </Grid>
                </local:ClippingBorder>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

只需根据自己的喜好编辑速度和颜色即可。