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

  1. Retarget the trigger's Setter to a different element (use TargetName) or a different property
  2. Break the cycle with an intermediate element or separate states (VisualStateManager)
  3. 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

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


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 Setters

View on GitHub (pinned to 81131a70a4)