dotnet/wpf · error · InvalidOperationException
SR.TriggerActionMustBelongToASingleTrigger
Error message
SR.TriggerActionMustBelongToASingleTrigger
What it means
TriggerAction.Seal(TriggerBase) enforces that a TriggerAction instance belongs to exactly one trigger. If the action is already sealed and a different containingTrigger tries to seal it, InvalidOperationException with SR.TriggerActionMustBelongToASingleTrigger is thrown, because sharing a sealed action across triggers would corrupt its owning-trigger state.
Solutions
- Create a separate TriggerAction instance for each trigger that needs one.
- If sharing is intended, create a factory method returning a new action per trigger.
- Check action.IsSealed before adding; if sealed and already owned by another trigger, construct a copy.
Example fix
// before var action = new BeginStoryboard(storyboard); triggerA.Actions.Add(action); triggerB.Actions.Add(action); // throws on seal of triggerB // after triggerA.Actions.Add(new BeginStoryboard(storyboard)); triggerB.Actions.Add(new BeginStoryboard(storyboard));
Defensive patterns
Strategy: validation
Validate before calling
if (action.IsSealed) throw new InvalidOperationException("Create a new TriggerAction instance for each trigger"); Type guard
static bool CanAttach(TriggerAction a, TriggerBase t) => a != null && (!a.IsSealed || true /* cannot inspect owner; assume not shared */);
Try / catch
try { trigger.Actions.Add(action); }
catch (InvalidOperationException) { trigger.Actions.Add(CloneAction(action)); } Prevention
- Never add the same TriggerAction instance to two triggers
- Use a factory method to create one action per trigger
- Keep a 1:1 relationship between actions and their owning trigger
When it happens
Trigger: Reusing one BeginStoryboard/TriggerAction instance in two different EventTriggers (or a trigger and another trigger) — the second Seal attempt with a different containing trigger throws.
Common situations: Caching TriggerAction objects (e.g. one BeginStoryboard) and adding the same instance to multiple EventTriggers across styles or 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
- Animation_Invalid_DefaultValue
- Cannot remove signature from read-only file.
- Image_EncoderNoColorContext
- Image_EncoderNoGlobalMetadata
- Image_EncoderNoGlobalThumbnail
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/af269da2024bb837.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/TriggerAction.cs:87
get
{
return _containingTrigger;
}
}
/// <summary>
/// Seal this TriggerAction to prevent further updates
/// </summary>
/// <remarks>
/// TriggerActionCollection will call this method to seal individual
/// TriggerAction objects. We do some check here then call the
/// parameter-less Seal() so subclasses can also do what they need to do.
/// </remarks>
internal void Seal( TriggerBase containingTrigger )
{
if( IsSealed && containingTrigger != _containingTrigger )
{
throw new InvalidOperationException(SR.TriggerActionMustBelongToASingleTrigger);
}
_containingTrigger = containingTrigger;
Seal();
}
/// <summary>
/// A derived class overrideing Seal() should set object state such
/// that further changes are not allowed. This is also a time to make
/// validation checks to see if all parameters make sense.
/// </summary>
internal override void Seal()
{
if( IsSealed )
{
throw new InvalidOperationException(SR.TriggerActionAlreadySealed);
}
base.Seal();
}View on GitHub (pinned to 81131a70a4)