dotnet/wpf · error · InvalidOperationException
SR.Format(SR.CannotChangeAfterSealed, "TriggerCollection")
Error message
SR.Format(SR.CannotChangeAfterSealed, "TriggerCollection")
What it means
A TriggerCollection becomes sealed once the owning FrameworkElement/FrameworkContentElement (via its Style or template) has been initialized and the collection is no longer mutable. Any attempt to Clear, Insert, Remove, or Set items in a sealed collection throws this InvalidOperationException from CheckSealed. This protects trigger state that the framework depends on after styling has been applied.
Solutions
- Modify triggers before the element is styled/loaded, e.g. in XAML or before adding the element to the visual tree
- Create a new collection/element if post-load trigger changes are required
- Trigger style re-evaluation by rebuilding the Style and reassigning it rather than mutating a sealed collection
- Check collection.IsSealed before mutating and branch to a rebuild path
Example fix
// before
protected override void OnLoaded(EventArgs e) {
Triggers.Clear(); // sealed -> throws
}
// after
protected override void OnInitialized(EventArgs e) {
Triggers.Add(new Trigger { Property = IsMouseOverProperty, Value = true });
} Defensive patterns
Strategy: validation
Validate before calling
if (!triggers.IsSealed) { triggers.Add(newTrigger); } else { /* rebuild element or style */ } Type guard
bool CanMutate(TriggerCollection c) => !c.IsSealed;
Try / catch
try { triggers.Add(t); } catch (InvalidOperationException ex) when (ex.Message.Contains("TriggerCollection")) { /* rebuild collection instead */ } Prevention
- Configure Triggers in XAML or before the element is loaded
- Never share TriggerCollection instances between elements
- Check IsSealed before any mutation
When it happens
Trigger: Calling triggers.Clear(), triggers.Add(...), triggers.Remove(...), or triggers[i] = ... on a TriggerCollection after it has been sealed, typically because the element's style/template context has been established and the collection instance is shared with the style.
Common situations: Mutating element.Triggers at runtime after the element is loaded/styled; sharing one TriggerCollection instance between multiple elements; modifying triggers inside a Loaded handler or after ApplyTemplate.
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
- SR.CannotChangeAfterSealed
- SR.CannotChangeAfterSealed
- SR.Format(SR.CannotChangeAfterSealed…
- Cannot pass multidimensional array to the CopyTo method on…
- DoubleKeyFrameCollection
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/9d90b7b10dfcb386.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/TriggerCollection.cs:131
}
// This may be null (i.e. when used in a style or template)
internal FrameworkElement Owner
{
get { return _owner; }
}
#endregion InternalMethods
#region PrivateMethods
private void CheckSealed()
{
if (_sealed)
{
throw new InvalidOperationException(SR.Format(SR.CannotChangeAfterSealed, "TriggerCollection"));
}
}
private void TriggerBaseValidation(TriggerBase triggerBase)
{
ArgumentNullException.ThrowIfNull(triggerBase);
}
// Called by GenericCollection.tb when a trigger is added to the collection.
// We use this opportunity to hook it into the tree.
private void OnAdd( TriggerBase triggerBase )
{
// If we don't have an Owner (the Style/Template case), or the
// element isn't initialized yet, we don't need to do anything
if (Owner != null && Owner.IsInitialized)
{
EventTrigger.ProcessOneTrigger(Owner, triggerBase);
}View on GitHub (pinned to 81131a70a4)