我有一个模板,我想为某个属性(比如Opacity
)设置动画,以响应绑定模型对象的不同变化。基本上所述对象有两个属性Enabled
和Broken
,并且根据它们的值,它们都可以改变不透明度。
使用setter非常容易:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<Setter TargetName="X" Property="Opacity" Value="0.5"/>
</DataTrigger>
<DataTrigger Binding="{Binding Broken}" Value="True">
<Setter TargetName="X" Property="Opacity" Value="0.5"/>
<Setter TargetName="Y" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
因为如果两个DataTriggers
都适用,我们最终会覆盖已经0.5
的值。但是,通过动画,我还没有想出如何正确地做到这一点。我最初的方法就是使用
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding Broken}" Value="True">
<Setter TargetName="Y" Property="Visibility" Value="Visible"/>
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
然而,现在的问题是,一旦第二个触发器通过两个故事板,不透明度固定为1
,第一个触发器不再动画任何东西。据我所知,这是因为动画仍然存在并覆盖了值,并且第一个动画没有改变。将FillBehavior
更改为Stop
显然可以解决该问题,但之后(同样明显)Opacity
会在动画后恢复到之前的值。
然后我尝试使用动画的附加Setter
:
<DataTrigger Binding="{Binding Enabled}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
<Setter TargetName="X" Property="Opacity" Value="0.5"/>
</DataTrigger>
但是第一次Enabled
更改了适用的setter并且动画无法播放。不过,它会在随后的变化中发挥作用。
另一种尝试绕过两个触发动画相同属性的方法是使用To
作为输入动画,使用From
作为退出动画。这似乎有效。但是,例如, Enabled
变化得足够快,输入动画被从不透明度0.5
开始的退出动画取代,因此在动画回到任何应该设置为动画的值之前,突然将不透明度更改为0.5
。
不知怎的,我现在尝试的所有选项要么不起作用,要么没有错误的细节,而且我无法找到关于如何一般接近动画事物以响应模型更改的良好指导,特别是如果这些事情也需要在另一个方向动画。或者,就像在我的情况下,甚至从两个不同的属性变化中完成。
答案 0 :(得分:3)
您可能需要删除通过其他数据触发器应用的故事板,以便动画不会锁定这些值。
根据您的输入我为您尝试了一个样本
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid>
<Ellipse Stretch="Uniform"
Fill="Gray"
x:Name="Y"
Visibility="Collapsed" />
<Ellipse Stretch="Uniform"
Fill="Orange"
Margin="8"
x:Name="X" />
<StackPanel HorizontalAlignment="Right">
<CheckBox Content="Enabled"
x:Name="enabled"
IsChecked="True" />
<CheckBox Content="Broken"
x:Name="broken" />
</StackPanel>
</Grid>
<DataTemplate.Resources>
<Storyboard x:Key="fadeOut">
<DoubleAnimation Storyboard.TargetName="X"
Storyboard.TargetProperty="Opacity"
To="0.5"
Duration="0:0:0.2" />
</Storyboard>
<Storyboard x:Key="fadeIn">
<DoubleAnimation Storyboard.TargetName="X"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.2" />
</Storyboard>
</DataTemplate.Resources>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsChecked,ElementName=enabled}"
Value="False">
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="fadeIn2" />
<RemoveStoryboard BeginStoryboardName="fadeOut2" />
<BeginStoryboard Storyboard="{StaticResource fadeOut}"
x:Name="fadeOut" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="fadeIn2" />
<RemoveStoryboard BeginStoryboardName="fadeOut2" />
<BeginStoryboard Storyboard="{StaticResource fadeIn}"
x:Name="fadeIn" />
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked,ElementName=broken}"
Value="True">
<Setter TargetName="Y"
Property="Visibility"
Value="Visible" />
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="fadeIn" />
<RemoveStoryboard BeginStoryboardName="fadeOut" />
<BeginStoryboard Storyboard="{StaticResource fadeOut}"
x:Name="fadeOut2" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="fadeIn" />
<RemoveStoryboard BeginStoryboardName="fadeOut" />
<BeginStoryboard Storyboard="{StaticResource fadeIn}"
x:Name="fadeIn2" />
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
在上面的示例中,您可以看到我已使用RemoveStoryboard
操作删除其他触发器应用的故事板。
如果问题仅在于平滑动画值,您可以尝试HandoffBehavior="Compose"
BeginStoryboard
但似乎您有点复杂的情况,您可能会创建一个附加行为,根据您的需要为其设置动画
替代方法
我确实尝试通过另一种方法解决问题
XAML
<ContentControl xmlns:l="clr-namespace:CSharpWPF">
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid>
<Ellipse Stretch="Uniform"
Fill="Gray"
x:Name="Y"
Visibility="{Binding VisibilityY, ElementName=animation}" />
<Ellipse Stretch="Uniform"
Fill="Orange"
Margin="8"
x:Name="X"
Opacity="{Binding OpacityX, ElementName=animation}" />
<StackPanel HorizontalAlignment="Right">
<CheckBox Content="Enabled"
x:Name="enabled"
IsChecked="True" />
<CheckBox Content="Broken"
x:Name="broken" />
<l:CustomAnimation x:Name="animation"
IsEnabled="{Binding IsChecked,ElementName=enabled}"
IsBroken="{Binding IsChecked,ElementName=broken}" />
</StackPanel>
</Grid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
CustomAnimation类
namespace CSharpWPF
{
public class CustomAnimation : FrameworkElement
{
public CustomAnimation()
{
IsEnabledProperty.OverrideMetadata(typeof(CustomAnimation), new UIPropertyMetadata(true, (s, e) => AnimateX(s as FrameworkElement, (bool)e.NewValue)));
}
static void AnimateX(FrameworkElement elem, bool fadeIn)
{
elem.BeginAnimation(OpacityXProperty, new DoubleAnimation(fadeIn ? 1 : 0.5, TimeSpan.FromSeconds(0.2)));
}
public bool IsBroken
{
get { return (bool)GetValue(IsBrokenProperty); }
set { SetValue(IsBrokenProperty, value); }
}
// Using a DependencyProperty as the backing store for IsBroken. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsBrokenProperty =
DependencyProperty.Register("IsBroken", typeof(bool), typeof(CustomAnimation), new PropertyMetadata(false, (s, e) =>
{
AnimateX(s as FrameworkElement, !(bool)e.NewValue);
s.SetValue(VisibilityYProperty, ((bool)e.NewValue) ? Visibility.Visible : Visibility.Collapsed);
}));
// Using a DependencyProperty as the backing store for XOpacity. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OpacityXProperty =
DependencyProperty.Register("OpacityX", typeof(double), typeof(CustomAnimation), new PropertyMetadata(1.0));
// Using a DependencyProperty as the backing store for VisibilityY. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VisibilityYProperty =
DependencyProperty.Register("VisibilityY", typeof(Visibility), typeof(CustomAnimation), new PropertyMetadata(Visibility.Collapsed));
}
}
尝试上面的示例,看看这是否符合预期