dotnet/wpf · error · InvalidOperationException

SR.Format(SR.CannotChangeAfterSealed…

Error message

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

What it means

TriggerActionCollection.CheckSealed() throws InvalidOperationException (CannotChangeAfterSealed, 'TriggerActionCollection') from Clear, RemoveAt, Add, Insert, Remove and the constructor path when the collection is sealed. A trigger's action collection is sealed together with its owning trigger/style once applied, making it read-only.

Solutions

  1. Rebuild the Style/Trigger in a fresh instance and reassign it to the controls
  2. Perform all collection edits before the style is applied (before control.Style is set)
  3. Guard edits with a check of the trigger's IsSealed state

Example fix

// before
trigger.Actions.Add(newAction); // InvalidOperationException once style applied
// after
var newTrigger = new Trigger { Property = trigger.Property, Value = trigger.Value };
foreach (var a in trigger.Actions) newTrigger.Actions.Add(a);
newTrigger.Actions.Add(newAction);
var newStyle = new Style { TargetType = typeof(Button) };
newStyle.Triggers.Add(newTrigger);
button.Style = newStyle;
Defensive patterns

Strategy: validation

Validate before calling

if (!trigger.IsSealed) { trigger.Actions.Add(newAction); } else { /* rebuild trigger/style */ }

Type guard

static bool CanEditActions(TriggerBase t) => t != null && !t.IsSealed;

Try / catch

try { actions.Add(a); }
catch (InvalidOperationException ex) when (ex.Message.Contains("TriggerActionCollection")) { /* rebuild style */ }

Prevention

When it happens

Trigger: Calling Add/Insert/Remove/Clear/RemoveAt on a trigger's Actions (or Exits) collection after the parent Style/Template was sealed by application to an element.

Common situations: Dynamically adding actions to a trigger of an in-use style; a shared template whose triggers are mutated after being applied to multiple controls.

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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/TriggerActionCollection.cs:292

        internal DependencyObject Owner
        {
            get { return _owner; }
            set
            { 
                Debug.Assert (Owner == null);
                _owner = value; 
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //  Private members

        // Throw if a change is attempted at a time we're not allowing them
        private void CheckSealed()
        {
            if ( _sealed )
            {
                throw new InvalidOperationException(SR.Format(SR.CannotChangeAfterSealed, "TriggerActionCollection"));
            }            
        }

        // Throw if the given object isn't a TriggerAction
        private TriggerAction VerifyIsTriggerAction(object value)
        {
            TriggerAction action = value as TriggerAction;

            if ( action == null )
            {
                ArgumentNullException.ThrowIfNull(value);

                throw new ArgumentException(SR.MustBeTriggerAction);
            }

            return action;
        }

View on GitHub (pinned to 81131a70a4)