dotnet/wpf · error · InvalidOperationException

CannotChangeAfterSealed

CannotChangeAfterSealed

Error message

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

What it means

BeginStoryboard.ThrowIfSealed throws InvalidOperationException (SR.CannotChangeAfterSealed for "BeginStoryboard") when any mutable property is changed after the object has been sealed. Sealing happens when the Triggers collection containing it is sealed, after which the action is immutable.

Solutions

  1. Create a new BeginStoryboard instance instead of mutating the sealed one.
  2. Make modifications before adding the BeginStoryboard to a Trigger's EnterActions/ExitActions or before the containing template is sealed.
  3. Clone the configuration into an unsealed object and replace it in the trigger.

Example fix

// before
sealedBeginStoryboard.HandoffBehavior = HandoffBehavior.Compose; // throws
// after
var newAction = new BeginStoryboard { Storyboard = storyboard, HandoffBehavior = HandoffBehavior.Compose };
trigger.EnterActions[0] = newAction;
Defensive patterns

Strategy: try-catch

Validate before calling

if (!beginStoryboard.IsSealed)
    beginStoryboard.HandoffBehavior = HandoffBehavior.Compose;

Try / catch

try { beginStoryboard.Name = newName; }
catch (InvalidOperationException ex) { /* construct a replacement instead of mutating */ }

Prevention

When it happens

Trigger: Setting Storyboard, HandoffBehavior, or Name on a BeginStoryboard whose IsSealed is true - e.g. mutating a template/event-trigger's BeginStoryboard after the containing style, template, or trigger collection was sealed.

Common situations: Modifying shared resources (styles/templates) at runtime that already contain BeginStoryboard actions, or reusing a cached BeginStoryboard instance across animations after it was sealed.

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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Media/Animation/BeginStoryboard.cs:117

        {
            ThrowIfSealed();

            if(value != null && !System.Windows.Markup.NameValidationHelper.IsValidIdentifierName(value))
            {
                // Duplicate the error string thrown from DependencyObject.SetValueValidateParams
                throw new ArgumentException(SR.Format(SR.InvalidPropertyValue, value, "Name"));
            }
            
            // Null is OK - it's to remove whatever name was previously set.
            _name = value;
        }
    }

    private void ThrowIfSealed()
    {
        if (IsSealed)
        {
            throw new InvalidOperationException(SR.Format(SR.CannotChangeAfterSealed, "BeginStoryboard"));
        }
    }
    
    // Bug #1329664 workaround to make beta 2
    // Remove thread affinity when sealed, but before doing that, snapshot the
    //  current Storyboard value and remove *its* thread affinity too.
    internal override void Seal()
    {
        if( !IsSealed )
        {
            // Gets our Storyboard value.  This value may have come from a
            //  ResourceReferenceExpression or might have been a deferred
            //  reference that has since been realized.
            Storyboard snapshot = GetValue(StoryboardProperty) as Storyboard;

            if( snapshot == null )
            {
                // This is the same error thrown by Begin if the Storyboard

View on GitHub (pinned to 81131a70a4)