【问题标题】:WPF Animation stops spontaneouslyWPF 动画自发停止
【发布时间】:2014-07-18 14:07:30
【问题描述】:

我有一个 WPF 应用程序,其中包含一个 UserControl,其边框是动画的:

<UserControl x:Class="CarSystem.CustomControls.AlarmItem"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:cs="clr-namespace:CarSystem.CustomControls"
             mc:Ignorable="d"
             DataContext="{Binding Path=Alarm, RelativeSource={RelativeSource Self}}">

    <UserControl.Resources>
        <Style TargetType="{x:Type cs:AlarmItem}">
            <Setter Property="IsFlashing" Value="False" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=IsExpired}" Value="True">
                    <Setter Property="IsFlashing" Value="True" />
                </DataTrigger>
                <DataTrigger Binding="{Binding Path=IsPending}" Value="True">
                    <Setter Property="IsFlashing" Value="True" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>

    <Border HorizontalAlignment="Center" 
            Margin="5" 
            Height="100"
            Name="Border" 
            VerticalAlignment="Center" 
            Width="100">
        <Border.Resources>
            <Storyboard x:Key="FlashingStoryboard"
                        AutoReverse="True"
                        RepeatBehavior="Forever">
                <ColorAnimationUsingKeyFrames BeginTime="00:00:00"
                                              Duration="00:00:00.5"
                                              Storyboard.TargetName="Border"
                                              Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)">
                    <DiscreteColorKeyFrame KeyTime="00:00:00.25" Value="Black" />
                </ColorAnimationUsingKeyFrames>
            </Storyboard>
        </Border.Resources>
        <Border.Style>
            <Style TargetType="Border">
                <Setter Property="BorderBrush" Value="Black" />
                <Setter Property="BorderThickness" Value="2" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=IsExpired}" Value="True">
                        <Setter Property="BorderThickness" Value="4" />
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=IsPending}" Value="True">
                        <Setter Property="BorderThickness" Value="4" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Border.Style>

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="FlashStates">
                <VisualState x:Name="FlashingOn"
                             Storyboard="{StaticResource ResourceKey=FlashingStoryboard}" />
                <VisualState x:Name="FlashingOff" />
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Image Grid.Row="0" 
                   Name="AlarmImage" 
                   Source="{Binding Path=Image, RelativeSource={RelativeSource AncestorType={x:Type cs:AlarmItem}}}" 
                   Stretch="Fill" />
            <cs:ResponseTimer Expired="Timer_Expired"
                              Grid.Row="1"
                              HideIfExpired="True"
                              IsTabStop="False"
                              MinHeight="10"
                              x:Name="TheTimer"
                              TimeoutPeriod="00:02:30"
                              VerticalAlignment="Bottom" />
        </Grid>
    </Border>
</UserControl>

应用程序从我公司制造的专有设备接收数据。对象被接收并加载到视图模型类实例中。为接收到的每个对象创建此控件的新实例,对视图模型的引用放入新控件实例的DataContext 属性中,并将新控件添加到Window 中的ListBox

动画应该在对象的状态属性是特定值时运行。还有第二个状态值,动画也应该运行,并且在用户没有响应该项目的固定时间间隔后状态更改为该值。仅当状态采用只能通过用户交互设置的值时,动画才应该停止。

当收到并显示第一个对象时,动画效果很好。如果没有收到更多对象,则动画将继续运行,并且在计时器到期时边框颜色会按预期更改。

但是,动画只会在收到 2 个或更多对象后自行停止。这既是在更改导致边框颜色更改的状态的计时器到期之前,也是在采取任何用户操作之前。请注意,它并不总是在第二个对象上停止,有时在动画停止之前需要接收 3 或 4 个对象。

有人知道为什么动画会停止吗?我如何让每个人都跑到最后?有没有更好的方法来获得没有这个问题的相同效果?

【问题讨论】:

  • 有其他人遇到过这样的问题吗?
  • 您可以发布完整的工作示例吗?提供的代码还不够。或至少提供一种模拟您的问题的方法。

标签: c# wpf animation


【解决方案1】:

我遇到的最接近的情况是根据视图模型中的值触发动画。我在这里采用“更好的方式”选项,如果这不符合您的解决方案,我只是根据我所看到的而道歉。

现在您提到为来自外部设备的每个警报创建一个新控件?所以我假设这个控件被绑定到一个单一的实例,并以类似列表框的形式呈现。

如果你还在我身边,你的控件实例有它自己的视图模型实例,并且从外观上看,你从 IsPending 和 IsExpired 属性触发,当其中任何一个为真时,让它闪烁。

我要做的第一件事是简化来自 ViewModel 的绑定,添加为您在需要警报时设置的 IsAlertRequired 属性 - 可以在设置器中为您现有的属性更新。这里的原因是单个触发器比多重绑定更容易。

然后使用 DataTrigger 启动您的故事板。在您的应用中添加对 Microsoft.Expression.Interactions 的引用,然后将它们导入您的 XAML:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 

然后在你的 layoutroot 元素之前添加一个触发器:

    <i:Interaction.Triggers>
    <ei:DataTrigger Binding="{Binding ViewModel.IsAlertRequired}" Value="True">
        <ei:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource FlashingAnimation}"/>
    </ei:DataTrigger>

这应该基于视图模型数据绑定来启动您的动画。

假设你已经走到了这一步,并且它可以启动你的动画,如果它仍然停止,那么我会检查你的视图模型或外部组件中的某些东西是否阻塞了 UI 线程。

希望这会有所帮助。

【讨论】:

  • 谢谢,我试试。动画似乎立即停止。我的意思是Border 控件的BorderBrush 停止改变颜色。我的假设是它已经停止,但它可能正在运行并且其他东西坏了。就像控件可能忘记了它应该在其中切换的刷子之一。这只是我想到的。我想我必须使用 Snoop 来检查动画。
  • 我得到“名称“交互”在命名空间“schemas.microsoft.com/expression/2010/interactivity"”中不存在。我应该把那个触发器放在哪里?
  • 你应该把xmlns放在控制根标签中,xaml中的第一个,应该还有其他类似的。在您的项目中添加对 Microsoft.expression.interactivity 的引用。最后,触发器在你的 usercontrol.resources 之后和你的边界之前。
  • 如果它停止并且您的 UI 仍然响应,例如按钮仍然按下或文本框仍然编辑,那么可以肯定动画已经完成。
  • 动画将 RepeatBehavior 设置为 Forever 在我的代码通过更改视觉状态告诉它停止之前,它不应该停止。它没有。并且 UI 仍然是响应式的。我打赌这是动画以 Border.BorderBrush 属性的 SolicColorBrosu.Color 中的错误颜色值开始。
【解决方案2】:

感谢kidshaw 提供的信息,我能够找到一个可行的解决方案。本质上,动画应该在两种颜色之间来回切换Border 控件的BorderBrush,黑色和取决于DataContext 中对象属性的颜色。这个逻辑在后面的代码中,因为该逻辑依赖于数据上下文对象的两个不同属性,我想不出一个可以正常工作的 XAML 触发器。它没有包含在原始问题中,因为它似乎不相关。

正如 cmets 对kidshaw 的回答中所提到的,问题是当额外的警报进入窗口时,动画正在丢失BorderBrush 应该是什么颜色。例如,它不会在红色和黑色之间来回切换,而是认为必须在黑色和黑色或红色和红色之间切换。所以看起来好像没有动画发生,实际上是这样。

为了解决这个问题,并且由于需要根据数据上下文中对象的属性选择颜色,我最终在动画中为另一种颜色添加了第二个DiscreteColorKeyFrame。我尝试使用将DiscreteColorKeyFrameValue 属性绑定到DependencyProperty 我添加到将由逻辑背后的代码设置的控件,但这不起作用。动画不断在黑色和透明之间来回切换,而 VS 中的输出窗口不断记录有关 CanFreeze 为假的错误。

所以我最终为每种不同的颜色制作了一个动画,并为每种颜色添加了一个VisualStateVisualStateManager。后面的代码也发生了变化,但我让一切正常了。

这是 XAML:

<UserControl x:Class="CarSystem.CustomControls.AlarmItem"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:cs="clr-namespace:CarSystem.CustomControls"
             mc:Ignorable="d"
             DataContext="{Binding Path=Alarm, RelativeSource={RelativeSource Self}}">

    <UserControl.Resources>
        <Style TargetType="{x:Type cs:AlarmItem}">
            <Setter Property="IsFlashing" Value="False" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=IsExpired}" Value="True">
                    <Setter Property="IsFlashing" Value="True" />
                </DataTrigger>
                <DataTrigger Binding="{Binding Path=IsPending}" Value="True">
                    <Setter Property="IsFlashing" Value="True" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>

    <Border HorizontalAlignment="Center" 
            Margin="5" 
            Height="100"
            Name="Border" 
            VerticalAlignment="Center" 
            Width="100">
        <Border.BorderBrush>
            <SolidColorBrush x:Name="AnimatedBrush" Color="Black" />
        </Border.BorderBrush>
        <Border.Resources>
            <Storyboard x:Key="ExpiredAnimation"
                        AutoReverse="False"
                        RepeatBehavior="Forever">
                <ColorAnimationUsingKeyFrames BeginTime="00:00:00"
                                              Duration="00:00:01"
                                              Storyboard.TargetName="AnimatedBrush"
                                              Storyboard.TargetProperty="Color">
                    <DiscreteColorKeyFrame KeyTime="00:00:00" Value="#FFFFFF78" />
                    <DiscreteColorKeyFrame KeyTime="00:00:00.5" Value="Black" />
                </ColorAnimationUsingKeyFrames>
            </Storyboard>

            <Storyboard x:Key="HistoricalAnimation"
                        AutoReverse="False"
                        RepeatBehavior="Forever">
                <ColorAnimationUsingKeyFrames BeginTime="00:00:00"
                                              Duration="00:00:01"
                                              Storyboard.TargetName="AnimatedBrush"
                                              Storyboard.TargetProperty="Color">
                    <DiscreteColorKeyFrame KeyTime="00:00:00"   Value="#FFFFFF78" />
                    <DiscreteColorKeyFrame KeyTime="00:00:00.5" Value="Black" />
                </ColorAnimationUsingKeyFrames>
            </Storyboard>

            <Storyboard x:Key="PendingAnimation"
                        AutoReverse="False"
                        RepeatBehavior="Forever">
                <ColorAnimationUsingKeyFrames BeginTime="00:00:00"
                                              Duration="00:00:01"
                                              Storyboard.TargetName="AnimatedBrush"
                                              Storyboard.TargetProperty="Color">
                    <DiscreteColorKeyFrame KeyTime="00:00:00" Value="Red" />
                    <DiscreteColorKeyFrame KeyTime="00:00:00.5" Value="Black" />
                </ColorAnimationUsingKeyFrames>
            </Storyboard>

            <Storyboard x:Key="WhiteListAnimation"
                        AutoReverse="False"
                        RepeatBehavior="Forever">
                <ColorAnimationUsingKeyFrames BeginTime="00:00:00"
                                              Duration="00:00:01"
                                              Storyboard.TargetName="AnimatedBrush"
                                              Storyboard.TargetProperty="Color">
                    <DiscreteColorKeyFrame KeyTime="00:00:00" Value="#FF5819" />
                    <DiscreteColorKeyFrame KeyTime="00:00:00.5" Value="Black" />
                </ColorAnimationUsingKeyFrames>
            </Storyboard>
        </Border.Resources>

        <Border.Style>
            <Style TargetType="Border">
                <Setter Property="BorderThickness" Value="2" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=IsExpired}" Value="True">
                        <Setter Property="BorderThickness" Value="4" />
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=IsPending}" Value="True">
                        <Setter Property="BorderThickness" Value="4" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Border.Style>

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="FlashStates">
                <VisualState x:Name="ExpiredState"    Storyboard="{StaticResource ResourceKey=ExpiredAnimation}" />
                <VisualState x:Name="HistoricalState" Storyboard="{StaticResource ResourceKey=HistoricalAnimation}" />
                <VisualState x:Name="PendingState"    Storyboard="{StaticResource ResourceKey=PendingAnimation}" />
                <VisualState x:Name="WhiteListState"  Storyboard="{StaticResource ResourceKey=WhiteListAnimation}" />
                <VisualState x:Name="FlashingOff" />
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Image Grid.Row="0" 
                   Name="AlarmImage" 
                   Source="{Binding Path=Image, RelativeSource={RelativeSource AncestorType={x:Type cs:AlarmItem}}}" 
                   Stretch="Fill" />
            <cs:ResponseTimer Expired="Timer_Expired"
                              Grid.Row="1"
                              HideIfExpired="True"
                              IsTabStop="False"
                              MinHeight="10"
                              x:Name="TheTimer"
                              TimeoutPeriod="00:02:30"
                              VerticalAlignment="Bottom" />
        </Grid>
    </Border>
</UserControl>

这是来自后端的代码,用于选择并启动正确的动画。

private void StartStatusAnimation() {
    if ( condition1 ) {
        // It is.  Display the WhiteListAnimation.
        if ( !VisualStateManager.GoToElementState( Border, "WhiteListState", true ) ) {
            // Log error
        }
    } else if ( condition2 ) {
        if ( !VisualStateManager.GoToElementState( Border, "ExpiredState", true ) ) {
            // Log error
        }

    } else if ( condition3 ) {
        if ( !VisualStateManager.GoToElementState( Border, "HistoricalState", true ) ) {
            // Log error
        }
    } else if ( condition4 ) {
        if ( !VisualStateManager.GoToElementState( Border, "PendingState", true ) ) {
            // Log error
        }
    } else {
        // We don't know what state this is.  Stop flashing now
        if ( !VisualStateManager.GoToElementState( Border, "FlashingOff", true ) ) {
            // Log error
        }
    }
}

请注意,数据上下文对象属性的值可能会因用户交互或计时器到期而发生变化,这会在第一种情况下一起停止闪烁,或者导致当前动画停止而另一个动画停止开始,在第二个。第二种情况,当Style中的触发器将控件的IsFlashing设置为false时,VisualSTateManager只是设置为FlashingOff状态,而第二种情况,当定时器到期时再次调用上述方法.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-31
    相关资源
    最近更新 更多