dotnet/wpf · error · InvalidOperationException

CannotChangeAfterSealed

CannotChangeAfterSealed

Error message

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

What it means

ControllableStoryboardAction.BeginStoryboardName throws InvalidOperationException (SR.CannotChangeAfterSealed for "ControllableStoryboardAction") when the name is changed after the action is sealed. The name links a control action (Pause/Resume/Stop) to a BeginStoryboard, and sealed actions are immutable.

Solutions

  1. Create a new ControllableStoryboardAction instance with the desired BeginStoryboardName.
  2. Set BeginStoryboardName before adding the action to the Trigger or before the containing template seals.
  3. Replace the sealed action in the trigger's actions collection with a fresh, configured one.

Example fix

// before
sealedPauseAction.BeginStoryboardName = "begin"; // throws
// after
var pauseAction = new PauseStoryboard { BeginStoryboardName = "begin" };
trigger.Actions[0] = pauseAction;
Defensive patterns

Strategy: try-catch

Validate before calling

if (!controlAction.IsSealed)
    controlAction.BeginStoryboardName = "begin";

Try / catch

try { controlAction.BeginStoryboardName = name; }
catch (InvalidOperationException ex) { /* create a new action instance instead */ }

Prevention

When it happens

Trigger: Setting BeginStoryboardName on a PauseStoryboard/ResumeStoryboard/SeekStoryboard/StopStoryboard whose IsSealed is true - i.e. after its containing trigger collection was sealed.

Common situations: Mutating shared style/template storyboard control actions at runtime, or reusing a cached control action instance after it has been used in a sealed template.

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/4949a7ac8725a2c3. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Media/Animation/ControllableStoryboardAction.cs:48

    {
    }
    
    /// <summary>
    ///     Name to use for resolving the storyboard reference needed.  This
    /// points to a BeginStoryboard instance, and we're controlling that one.
    /// </summary>
    [DefaultValue(null)]
    public string BeginStoryboardName
    {
        get
        {
            return _beginStoryboardName;
        }
        set
        {
            if (IsSealed)
            {
                throw new InvalidOperationException(SR.Format(SR.CannotChangeAfterSealed, "ControllableStoryboardAction"));
            }

            // Null is allowed to wipe out existing name - as long as another
            //  valid name is set before Invoke time.
            _beginStoryboardName = value;
        }
    }

    internal sealed override void Invoke( FrameworkElement fe, FrameworkContentElement fce, Style targetStyle, FrameworkTemplate frameworkTemplate, Int64 layer )
    {
        Debug.Assert( fe != null || fce != null, "Caller of internal function failed to verify that we have a FE or FCE - we have neither." );
        Debug.Assert( targetStyle != null || frameworkTemplate != null,
            "This function expects to be called when the associated action is inside a Style/Template.  But it was not given a reference to anything." );

        INameScope nameScope = null;
        if( targetStyle != null )
        {
            nameScope = targetStyle;

View on GitHub (pinned to 81131a70a4)