dotnet/wpf · error · InvalidOperationException
SR.Format(SR.PropertyTriggerCycleDetected, source.Name)
Error message
SR.Format(SR.PropertyTriggerCycleDetected, source.Name)
What it means
During TriggerBase.Seal(), WPF detects a property-trigger cycle: a trigger's property value targets its own source element (source == dependent with ChildName == SelfName), which would cause infinite trigger re-evaluation. It throws InvalidOperationException (PropertyTriggerCycleDetected, source.Name) naming the offending element.
Solutions
- Retarget the trigger's Setter to a different element (use TargetName) or a different property
- Break the cycle with an intermediate element or separate states (VisualStateManager)
- Move the mutual dependency into a MultiTrigger/Converter that computes a stable value
Example fix
// before <Trigger Property="IsChecked" Value="True"> <Setter TargetName="self" Property="IsChecked" Value="False"/> </Trigger> // after <!-- target a different property or element --> <Trigger Property="IsChecked" Value="True"> <Setter Property="Background" Value="Green"/> </Trigger>
Defensive patterns
Strategy: validation
Validate before calling
if (triggerProperty == setterProperty && targetName == "self")
throw new InvalidOperationException("Trigger cycle detected."); Try / catch
try { style.Seal(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("cycle")) { /* fix trigger targeting */ } Prevention
- Never set the same property a trigger watches on the same element
- Review template triggers for self-reference after refactoring
- Use VisualStateManager for complex state transitions
When it happens
Trigger: A trigger in a template sets a property on the same element that the trigger's condition watches (self-referential TargetName/SelfName), e.g. a trigger on TemplateBinding-property X that sets X on the same node.
Common situations: Template authoring where a trigger's setter and condition reference the same property on the same element; refactoring templates and accidentally looping conditions.
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.Format(SR.TemplateCircularReferenceFound, name…
- SR.ChildTemplateInstanceDoesNotExist
- SR.ElementMustBelongToTemplate
- SR.Format(SR.CannotChangeAfterSealed…
- SR.Format(SR.CannotChangeAfterSealed, "Template")
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/6c5835617dde9ee6.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/TriggerBase.cs:247
// Track Dependent/Source relationship for prediction
for (int i = 0; i < PropertyValues.Count; i++)
{
PropertyValue propertyValue = PropertyValues[i];
DependencyProperty dependent = propertyValue.Property;
for (int j = 0; j < propertyValue.Conditions.Length; j++)
{
DependencyProperty source = propertyValue.Conditions[j].Property;
// Check for obvious cycles. Don't test for cycles if we have
// something other than self as the target, since this means that
// the templatedParent is presumably not the target. See windows bug
// 984916 for details.
if (source == dependent && propertyValue.ChildName == StyleHelper.SelfName)
{
throw new InvalidOperationException(SR.Format(SR.PropertyTriggerCycleDetected, source.Name));
}
}
}
_enterActions?.Seal(this);
_exitActions?.Seal(this);
// Remove thread affinity so it can be accessed across threads
DetachFromDispatcher();
}
// This will transfer information in the _setters collection to PropertyValues array.
internal void ProcessSettersCollection(SetterBaseCollection setters)
{
// Add information in Setters collection to PropertyValues array.
if( setters != null )
{
// Seal SettersView on GitHub (pinned to 81131a70a4)