C# 如何使用绑定到viewmodel的动态宽度和值在WPF(mvvm)中创建倒计时条动画?
我想创建一个从当前宽度变为0的条形(矩形)动画,该动画将用作倒计时的可视化。 最后,它应该是这样的:C# 如何使用绑定到viewmodel的动态宽度和值在WPF(mvvm)中创建倒计时条动画?,c#,wpf,xaml,animation,mvvm,C#,Wpf,Xaml,Animation,Mvvm,我想创建一个从当前宽度变为0的条形(矩形)动画,该动画将用作倒计时的可视化。 最后,它应该是这样的: 现在,启动动画的触发器是在静态类中设置的(这部分已经工作了) 我有几点我在这里没有用到: 此视图将位于窗口底部,可缩放。因此,我不知道动画开始时的起始宽度(以像素为单位)。我希望从FilledCountdownBar元素中删除“Width”,让它在开始时自动填充整个空间,但是我无法设置该值的动画(得到一个异常) 当我没有设置动画的“From”属性时,动画不会重置,因为没有开始值,并且在第一
现在,启动动画的触发器是在静态类中设置的(这部分已经工作了)
我有几点我在这里没有用到:
欢迎提供任何帮助,提前感谢。当我需要使用依赖于宽度和类似内容的动态动画时,我总是将其作为附加行为或自定义控件代码在代码中执行 这允许您在代码中创建
情节提要
,设置其所有动态属性,然后启动它
在这种情况下,一旦动画开始,它将成为控件开始时的大小。如果用户在窗口运行时调整其大小,动画将不会动态缩放自身。然而,你确实可以做到这一点。我刚刚实现了您的简单的双动画
以下是您案例的工作示例:
XAML
非常感谢你!这是非常好的工作,我想我确实理解它是如何工作的。关于“如果用户在窗口运行时调整窗口大小,动画将不会自动缩放。”-部分:我知道这种行为,并且实际上愿意接受这种限制。但是如果你能建议一种更好的方法,我很乐意看一看。:-)
<Control x:Name="content" Grid.Column="0" Margin="0">
<Control.Template>
<ControlTemplate>
<Grid x:Name="FilledCountdownBar" Width="500" HorizontalAlignment="Left" >
<Rectangle Fill="#FFA4B5BF"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=(Managers:ActionModeManager.ShowUiTimer)}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="FilledCountdownBar"
Storyboard.TargetProperty="(FrameworkElement.Width)"
To="0" Duration="0:1:0" AutoReverse="False"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Control.Template>
</Control>
<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), typeof(CountDownBarAnimationBehavior), 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", typeof(FrameworkElement), typeof(CountDownBarAnimationBehavior), 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();
}
}
}