wpf WPF在模板中平滑动画ProgressBar

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26618308/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 12:39:34  来源:igfitidea点击:

WPF Smoothly animated ProgressBar in template

c#wpfmvvmprogress-bar

提问by Terrordoll

I am working on an MVVM application and I would like to have a ProgressBar that smoothly animates to it's new value when that property changes. I have seen several answers to this question using c# but I'd prefer to do it all inside the template. The problem I'm having is setting up and targeting the event and storyboard properly. Here is what I have currently:

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

The progress bar-

进度条——

The style- (just the triggers)

风格-(只是触发器)

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

I took the trigger code from here: http://msdn.microsoft.com/en-us/library/system.windows.controls.progressbar(v=vs.110).aspx.

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

How do I set the TargetName to the template itself so that it applies to all the controls which use this template? How do I set "To" to the incoming Value? There appears to be a way to grab the "Binding" value but I have Value and Max both bound on the progressbar element. How would it know what to use?

如何将 TargetName 设置为模板本身,以便它适用于使用此模板的所有控件?如何将“To”设置为传入值?似乎有一种方法可以获取“绑定”值,但我在进度条元素上绑定了 Value 和 Max。它怎么知道要使用什么?

Here is the whole template for reference:

以下是整个模板供参考:

    <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>

Any help would be appreciated!

任何帮助,将不胜感激!

采纳答案by Terrordoll

I never actually found a solution to this. I ended up just writing my own control. This isn't technically an answer to the question, but I figure I may as well post it. If someone is looking for an animating progress control for MVVM this may help.

我从来没有真正找到解决这个问题的方法。我最终只是编写了自己的控件。从技术上讲,这不是问题的答案,但我想我也可以发布它。如果有人正在寻找 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));
            }
        }
    }
}

And here is the XAML:

这是 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>

回答by Niewidzialny

I think its better way.

我认为这是更好的方法。

You can create behavior to do this. (MVVM WPF)

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

Create class:

创建类:

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

And simply usage:

简单的用法:

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>

回答by James M

I know this question is solved, but I found a really good implementation that doesn't require creating a UserControl. It imitates the "barber pole effect" and works right out of the box:

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

Simply edit speed and colors to your liking.

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