WPF模板中平滑动画的进度条

8
我正在开发一个MVVM应用程序,希望当进度条的属性更改时,能够平滑地动画到新值。我看到了一些使用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"设置为传入的Value值?似乎有一种方法可以获取"Binding"值,但我在progressbar元素上绑定了Value和Max两个属性。它会知道使用哪个吗?
以下是整个模板的参考:
    <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>

非常感谢您的帮助!


我认为你不能使用Xaml中的Storyboards来完成这个任务,但你可以将此行为封装到一个附加行为中。动画仍然可以通过C#代码应用,但这些代码将被封装在可重用的行为中,并且可以由你的样式激活。 - Mike Strobel
3个回答

10

我认为这是更好的方式。

您可以创建行为来实现此目标。(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>

行为驱动开发肯定是更好的方式。谢谢你。 - johnDisplayClass
我会存储一个请求队列,以便在动画过程中更改值时重新启动动画。不确定您是否可以在当前实现的基础上做得更好。 - Imagin

2

我其实没有找到解决办法,最终只好自己写了一个控件。虽然这不是问题的答案,但我想还是发一下吧。如果有人正在寻找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>


1

我知道这个问题已经解决了,但我发现了一个非常好的实现方式,它不需要创建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>

只需按照您的喜好编辑速度和颜色。


如果我理解错了,请纠正我,这是一个带有动画背景的进度条模板?我想要的是一个进度条,它可以从当前值动画到新值,即填充或排空,而不是跳跃。不过我还是会保留这个模板的,谢谢。永远不知道什么时候会需要这样的东西 :) - Terrordoll
如果你想让它看起来像是滑动到下一个新值,你可以在ValueChanged事件中加入StoryBoard触发器。我自己还没有测试过,但那可能是一个不错的起点。祝你好运! - user1618054
如果有人正在使用这个(它运行得很好),但需要 ClippingBorder,请访问这里:https://dev59.com/c3RC5IYBdhLWcg3wW_pk - Michael K

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