dotnet / dotnet/wpf

Storyboard never fires Completed event in some situations, even though state goes from Active to Filling

Open
#4,537 0 comments 1 reaction 0 assignees View on GitHub
.NET Framework Bug
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

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:**
```

















```
```
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 groups = VisualStateManager.GetVisualStateGroups(MainGrid).Cast();

foreach (VisualStateGroup group in groups)
foreach (VisualState state in group.States.Cast())
{
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}");
}
}
}

```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.