我有一个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>
应用程序从我公司制造的专有设备接收数据。接收对象并将其加载到View Model类实例中。为每个接收的对象创建此控件的新实例,将对视图模型的引用放入新控件实例的DataContext
属性中,并将新控件添加到ListBox
中的Window
1}}。
当对象的status属性是特定值时,动画应该运行。还有第二个状态值,其中动画也应该运行,并且在用户未响应该项目的固定时间间隔之后状态将更改为该值。仅当状态采用只能由用户交互设置的值时,动画才会停止。
收到第一个物品时&amp;显示,动画工作正常。如果没有收到其他对象,动画将继续运行&amp;当计时器到期时,边框颜色会按预期更改。
但是,动画在收到2个或更多对象后自行停止。这是在更改导致边框颜色更改的状态的计时器之前以及在执行任何用户操作之前。请注意,它并不总是停留在第二个对象上,有时需要在动画停止之前接收3个或4个对象。
有谁知道为什么动画会停止?如何让每个人都跑到最后?有没有更好的方法来获得没有这个问题的相同效果?
答案 0 :(得分:0)
我最接近的情况是根据视图模型中的值触发动画。我采取了更好的方式&#39;如果这不符合您的解决方案,请在此处表示道歉,我只是基于我所看到的。
现在您提到为来自外部设备的每个警报创建了一个新控件?因此,我将假设此控件绑定到单个实例,并以类似列表框的形式呈现。
如果您仍然和我在一起,您的控件实例会拥有它自己的视图模型实例,并且从它的外观来看,您从IsPending和IsExpired属性触发,以便当其中任何一个为真时,使其成为闪光灯。
我要做的第一件事是简化从ViewModel添加为您需要警报时设置的IsAlertRequired属性的绑定 - 可以在setter中为现有属性更新。这里的原因是单个触发器比多重绑定更容易。
然后使用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>
那应该根据视图模型数据绑定开始动画。
假设你到目前为止并且它可以用于启动动画,如果它仍然停止,那么我将查看viewmodel或外部组件中的某些内容是否阻止了UI线程。
希望这有帮助。
答案 1 :(得分:0)
感谢来自kidshaw的信息,我找到了一个有效的解决方案。基本上,动画应该在两种颜色之间来回切换Border
控制BorderBrush
,黑色和颜色取决于DataContext
中对象的属性。这个逻辑在代码背后,因为逻辑依赖于数据上下文对象的两个不同属性,我无法想出能够正常工作的XAML触发器。它没有被包含在原始问题中,因为它似乎并不恰当。
正如对孩子们回答的评论中所提到的那样,问题在于,当其他警报进入窗口时,动画正在失去BorderBrush
应该是什么颜色的轨迹。而不是切换回&amp; Red&amp;例如,黑色会认为它必须在黑色和黑色之间切换。黑色或红色&amp;红色。所以看起来没有动画正在发生,实际上是。
为了解决这个问题,并且由于需要根据数据上下文中对象的属性选择颜色,我最终在动画中为另一种颜色添加了第二个DiscreteColorKeyFrame
。我尝试使用绑定Value
的{{1}}属性到DiscreteColorKeyFrame
我添加到控件中,该控件将由逻辑后面的代码设置,但是没有工作。动画一直在转回&amp;黑色和黑色之间透明,而VS中的输出窗口保持记录错误DependencyProperty
为假。
所以我最终为每种不同的颜色制作了一个动画,并为每种颜色的CanFreeze
添加了一个VisualState
。代码背后也有变化,但是这一切都有效。
这是XAML:
VisualStateManager
这是来自后端的代码选择&amp;开始正确的动画。
<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
}
}
}
中的触发器将控件的VisualSTateManager
设置为false时,FlashingOff
仅设置为Style
状态,并且第二,当计时器到期时再次调用上述方法。