-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
- .NET Core Version: 5.0.6
- Windows version: (
20H2) - Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
Problem description: Storyboard completion event does not fire if the animation is a ObjectAnimationUsingKeyFrames with a single keyframe of keytime 0:0:0. Works correctly if keytime > 0
Actual behavior:
Storyboard.CurrentStateInvalidated fires but Storyboard.Completed does not
It looks like RaiseCurrentStateInvalidated() is not executed before RaiseCompletedForRoot()is called when the keytime is 0:0:0 due to a lack of intersection between the parent and child timelines. Though this may be moot given the relevant criteria for RaiseCompletedForRoot()is for CurrentStateInvalidatedEventRaised to be true.
Unfortunately, if the timelines to not intersect, RaiseCurrentStateInvalidated() can be called if clock state changed, but it is after RaiseCompletedForRoot() in ComputeLocalState() therefore the completion event does not get raised when it probably should be.
Expected behavior:
Storyboard.Completed fires
Presumably this can be done by moving the call for RaiseCompletedForRoot() to be after all RaiseCurrentStateInvalidated() calls to make sure we don't miss a state change without an intersection? I'm not exactly an expert on the animations system, so I have no clue if that breaks something else.
Minimal repro:
<Window x:Class="TestIcon.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestIcon"
mc:Ignorable="d"
x:Name="Main"
xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
Title="Test" Height="450" Width="800">
<Grid x:Name="MainGrid">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="test_group">
<VisualState x:Name="test_state">
<Storyboard x:Name="test_storyboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
Storyboard.TargetName="test_target">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="Loaded">
<behaviors:GoToStateAction StateName="test_state"/>
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<Rectangle x:Name="test_target" Visibility="Collapsed" Width="200" Height="200" Fill="Red"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Media.Animation;
namespace TestIcon
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
IEnumerable<VisualStateGroup> groups = VisualStateManager.GetVisualStateGroups(MainGrid).Cast<VisualStateGroup>();
foreach (VisualStateGroup group in groups)
foreach (VisualState state in group.States.Cast<VisualState>())
{
state.Storyboard.Completed += Storyboard_Completed;
state.Storyboard.CurrentStateInvalidated += Storyboard_CurrentStateInvalidated;
state.Storyboard.CurrentTimeInvalidated += Storyboard_CurrentTimeInvalidated;
}
}
private void Storyboard_CurrentTimeInvalidated(object sender, EventArgs e)
{
if (sender is Clock clock) PrintDebugInfo(clock, "time invalidated");
else throw new InvalidOperationException();
}
private void Storyboard_CurrentStateInvalidated(object sender, EventArgs e)
{
if (sender is Clock clock) PrintDebugInfo(clock, "state invalidated");
else throw new InvalidOperationException();
}
private void Storyboard_Completed(object sender, EventArgs e)
{
if (sender is Clock clock) PrintDebugInfo(clock, "completed");
else throw new InvalidOperationException();
}
private void PrintDebugInfo(Clock clock, string eventType)
{
Debug.WriteLine($"Clock {eventType}. Timeline Name: {clock.Timeline.Name} Progress: {clock.CurrentProgress} Current State: {clock.CurrentState} Current time: {clock.CurrentTime}");
}
}
}