dotnet/wpf · error · InvalidOperationException

CannotChangeAfterSealed

CannotChangeAfterSealed

Error message

SR.Format(SR.CannotChangeAfterSealed, "SetStoryboardSpeedRatio")

What it means

SetStoryboardSpeedRatio.SpeedRatio's setter throws InvalidOperationException when the action object is sealed. Like other controllable storyboard actions, it becomes read-only once attached to a trigger/storyboard and put into use.

Solutions

  1. Create a new SetStoryboardSpeedRatio with the desired SpeedRatio for each change.
  2. Set SpeedRatio before the action is attached to any EventTrigger or storyboard.
  3. Alternatively call Storyboard.SetSpeedRatio directly on the target's clock.

Example fix

// before
sharedSpeedAction.SpeedRatio = 2.0; // throws once sealed

// after
var action = new SetStoryboardSpeedRatio { Storyboard = targetStoryboard, SpeedRatio = 2.0 };
Defensive patterns

Strategy: type-guard

Validate before calling

if (action.IsSealed) action = new SetStoryboardSpeedRatio { Storyboard = action.Storyboard, SpeedRatio = newRatio };

Type guard

static SetStoryboardSpeedRatio EnsureMutable(SetStoryboardSpeedRatio a, double ratio) => a.IsSealed ? new SetStoryboardSpeedRatio { Storyboard = a.Storyboard, SpeedRatio = ratio } : a;

Try / catch

try { action.SpeedRatio = ratio; } catch (InvalidOperationException) { action = new SetStoryboardSpeedRatio { /* reconfigure */ }; }

Prevention

When it happens

Trigger: Setting SpeedRatio on a SetStoryboardSpeedRatio instance after it has been sealed — e.g. mutating a shared or template-declared action at runtime.

Common situations: Dynamically updating the speed ratio by reusing one SetStoryboardSpeedRatio object instead of creating a new one per trigger invocation.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/45e75c5620142f76. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Media/Animation/SetStoryboardSpeedRatio.cs:37

    /// </summary>
    public sealed class SetStoryboardSpeedRatio : ControllableStoryboardAction
{
    /// <summary>
    ///     A speed ratio to use for this action.  If it is never explicitly
    /// specified, it is 1.0.
    /// </summary>
    [DefaultValue(1.0)]
    public double SpeedRatio
    {
        get
        {
            return _speedRatio;
        }
        set
        {
            if (IsSealed)
            {
                throw new InvalidOperationException(SR.Format(SR.CannotChangeAfterSealed, "SetStoryboardSpeedRatio"));
            }

            _speedRatio = value;
        }
    }

    /// <summary>
    ///     Called when it's time to execute this storyboard action
    /// </summary>
    internal override void Invoke( FrameworkElement containingFE, FrameworkContentElement containingFCE, Storyboard storyboard )
    {
        Debug.Assert( containingFE != null || containingFCE != null,
            "Caller of internal function failed to verify that we have a FE or FCE - we have neither." );

        if( containingFE != null )
        {
            storyboard.SetSpeedRatio(containingFE, SpeedRatio);
        }

View on GitHub (pinned to 81131a70a4)