如何在WPF(mvvm)中创建具有动态宽度和绑定到ViewModel的值的倒计时条动画?

我想创建一个从其当前宽度到0的条形(矩形)动画,该动画将用作倒计时的可视化。 最后,它看起来应该像这样:

如何在WPF(mvvm)中创建具有动态宽度和绑定到ViewModel的值的倒计时条动画?


现在,我在动画中设置了启动动画的触发器(该部分已经起作用)。

Winsoc

我有几点,我不在这里工作:

  1. 此视图将位于可缩放的窗口底部。因此,我不知道动画开始时的开始宽度(以像素为单位)。我想从 FilledCountdownBar 元素中删除“宽度”,以使其在开始时自动填充整个空间,但随后我无法为该值设置动画(获取异常)。
  2. 当我未设置动画的“ From”属性时,动画不会重置,因为没有起始值,并且在动画第一次播放完后宽度将保持为0。
  3. 我的视图模型中有一个属性(持续时间类型),我想绑定到动画的持续时间。但是看来我无法在控制模板中做到这一点?引发异常:
  

无法冻结此情节提要板时间轴树以供跨线程使用。

我也尝试使用ProgressBar代替,但是我无法使其动画流畅。更改值时总会出现一些小的步骤,因此对我来说这并不是一个选择。
欢迎您提供任何帮助,谢谢。

fuying58 回答:如何在WPF(mvvm)中创建具有动态宽度和绑定到ViewModel的值的倒计时条动画?

当我需要依赖于Widths之类的动态动画时,我总是将它们作为附加行为或自定义控件代码在代码中进行处理。

这使您可以在代码中创建Storyboard,设置其所有动态属性,然后启动它。

在这种情况下,动画一旦开始播放,就等于控件开始的大小。如果用户在运行窗口时调整其大小,则动画将不会自动缩放。但是,您确实可以做到这一点。我刚刚实现了您简单的DoubleAnimation

以下是您的案例的有效示例:

XAML

<Window x:Class="WpfApp4.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApp4"
    Title="MainWindow"
    Width="800"
    Height="450"
    UseLayoutRounding="True">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Control x:Name="CountDownVisual"
            Grid.Row="1"
            Height="30"
            Margin="0">
            <Control.Template>
                <ControlTemplate>
                    <Grid x:Name="RootElement">
                        <Grid x:Name="CountDownBarRootElement"
                            local:CountDownBarAnimationBehavior.IsActive="{Binding ShowUiTimer}"
                            local:CountDownBarAnimationBehavior.ParentElement="{Binding ElementName=RootElement}"
                            local:CountDownBarAnimationBehavior.TargetElement="{Binding ElementName=CountDownBar}">
                            <Rectangle x:Name="CountDownBar"
                                HorizontalAlignment="Left"
                                Fill="#FFA4B5BF" />
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Control.Template>
        </Control>
    </Grid>
</Window>

附加行为

using System;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Threading;

namespace WpfApp4
{
    public static class CountDownBarAnimationBehavior
    {
        private static Storyboard sb;

        #region IsActive (DependencyProperty)
        public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive",typeof(bool),typeof(CountDownBarAnimationBehavior),new FrameworkPropertyMetadata(false,OnIsActiveChanged));

        public static bool GetIsActive(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsActiveProperty);
        }

        public static void SetIsActive(DependencyObject obj,bool value)
        {
            obj.SetValue(IsActiveProperty,value);
        }

        private static void OnIsActiveChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
        {
            if (!(d is FrameworkElement control))
            {
                return;
            }

            if((bool)e.NewValue)
            {
                if (GetParentElement(control) != null)
                {
                    StartAnimation(control);
                }
                else
                {
                    // If IsActive is set to true and the other properties haven't
                    // been updated yet,defer the animation until render time.
                    control.Dispatcher?.BeginInvoke((Action) (() => { StartAnimation(control); }),DispatcherPriority.Render);
                }
            }
            else
            {
                StopAnimation();
            }
        }
        #endregion

        #region ParentElement (DependencyProperty)
        public static readonly DependencyProperty ParentElementProperty = DependencyProperty.RegisterAttached("ParentElement",typeof(FrameworkElement),new FrameworkPropertyMetadata(null,OnParentElementChanged));

        public static FrameworkElement GetParentElement(DependencyObject obj)
        {
            return (FrameworkElement)obj.GetValue(ParentElementProperty);
        }

        public static void SetParentElement(DependencyObject obj,FrameworkElement value)
        {
            obj.SetValue(ParentElementProperty,value);
        }

        private static void OnParentElementChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
        {
            if(!(d is FrameworkElement fe))
            {
                return;
            }

            // You can wire up events here if you want to react to size changes,etc.
        }

        private static void OnParentElementSizeChanged(object sender,SizeChangedEventArgs e)
        {
            if (!(sender is FrameworkElement fe))
            {
                return;
            }

            if (GetIsActive(fe))
            {
                StopAnimation();
                StartAnimation(fe);
            }
        }
        #endregion

        #region TargetElement (DependencyProperty)
        public static readonly DependencyProperty TargetElementProperty = DependencyProperty.RegisterAttached("TargetElement",new FrameworkPropertyMetadata(null));

        public static FrameworkElement GetTargetElement(DependencyObject obj)
        {
            return (FrameworkElement)obj.GetValue(TargetElementProperty);
        }

        public static void SetTargetElement(DependencyObject obj,FrameworkElement value)
        {
            obj.SetValue(TargetElementProperty,value);
        }
        #endregion

        private static void StartAnimation(DependencyObject d)
        {
            var parent = GetParentElement(d);
            var target = GetTargetElement(d);

            if (parent == null || target == null)
            {
                return;
            }

            sb = new Storyboard();
            var da = new DoubleAnimation();

            Storyboard.SetTarget(da,target);
            Storyboard.SetTargetProperty(da,new PropertyPath("Width"));

            da.AutoReverse = false;
            da.Duration = new Duration(new TimeSpan(0,1,0));
            da.From = parent.ActualWidth;
            da.To = 0d;

            sb.Children.Add(da);

            sb.Begin();
        }

        private static void StopAnimation()
        {
            sb?.Stop();
        }
    }
}
本文链接:https://www.f2er.com/3103830.html

大家都在问