dotnet/wpf · error · InvalidOperationException

SR.CyclicThemeStyleReferenceDetected

Error message

SR.CyclicThemeStyleReferenceDetected

What it means

A FrameworkElement's theme style resolution detected a reference cycle: while applying its theme style, the element (indirectly) triggers applying the same theme style again via its BasedOn chain or default style key. WPF guards this recursion with IsThemeStyleUpdateInProgress and throws InvalidOperationException to prevent infinite recursion. It almost always indicates a style with a BasedOn chain that loops back to itself.

Solutions

  1. Inspect the theme style's BasedOn chain for the element type and break the self/circular reference.
  2. Ensure DefaultStyleKey returns a distinct type whose style does not BasedOn the original element's style.
  3. Check generic.xaml / theme resource dictionaries for duplicated or recursive style keys after a rename/refactor.

Example fix

// before
<Style TargetType="local:MyControl" BasedOn="{StaticResource {x:Type local:MyControl}}" />
// after
<Style TargetType="local:MyControl" BasedOn="{StaticResource {x:Type Control}}" />
Defensive patterns

Strategy: validation

Validate before calling

var style = Application.Current.TryFindResource(myControl.DefaultStyleKey) as Style;
for (var s = style; s != null; s = s.BasedOn)
    if (ReferenceEquals(s, style) && !ReferenceEquals(s, style)) continue; // walk chain
// Walk with a HashSet<Style> and throw if a style repeats before applying theme styles.

Type guard

static bool HasStyleCycle(Style style) { var seen = new HashSet<Style>(); for (var s = style; s != null; s = s.BasedOn) { if (!seen.Add(s)) return true; } return false; }

Try / catch

try { element.ApplyTemplate(); } catch (InvalidOperationException ex) when (ex.Message.Contains("cyclic") || ex.Message.Contains(themeStyleKey.ToString())) { log.Error("Cyclic theme style for " + element.GetType(), ex); }

Prevention

When it happens

Trigger: Calling ApplyThemeStyle/UpdateThemeStyle on an element whose theme style's BasedOn chain forms a cycle (e.g. a style BasedOn itself, or A BasedOn B BasedOn A), typically set via DefaultStyleKey or ThemeStyle lookup.

Common situations: Custom control authors declaring a style whose BasedOn points to a style keyed to the same type; refactoring generic.xaml resource keys so a style ends up BasedOn itself; overriding metadata DefaultStyleKeyProperty to a type whose style chains back.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/f36f6b512ebc8a62. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WpfGfx/codegen/mcg/generators/FrameworkElementTemplate.cs:761

                                                if (toolTip != null)
                                                {
                                                    FrameworkObject toolTipFO = new FrameworkObject(toolTip);
                                                    if (toolTipFO.IsValid)
                                                    {
                                                        TreeWalkHelper.InvalidateOnResourcesChange(toolTipFO.FE, toolTipFO.FCE, ResourcesChangeInfo.ThemeChangeInfo);
                                                    }
                                                }

                                                OnThemeChanged();
                                            }
                                            finally
                                            {
                                                IsThemeStyleUpdateInProgress = false;
                                            }
                                        }
                                        else
                                        {
                                            throw new InvalidOperationException(SR.Format(SR.CyclicThemeStyleReferenceDetected, this));
                                        }
                                    }

                                    // Called when the theme changes so resources not in the tree can be updated by subclasses
                                    internal virtual void OnThemeChanged()
                                    {
                                    }

                                    ///<summary>
                                    ///     Initiate the processing for Loaded event broadcast starting at this node
                                    /// </summary>
                                    /// <remarks>
                                    ///     This method is to allow firing Loaded event from a Helper class since the override is protected
                                    /// </remarks>
                                    internal void FireLoadedOnDescendentsInternal()
                                    {
                                        // This is to prevent duplicate Broadcasts for the Loaded event
                                        if (LoadedPending == null)

View on GitHub (pinned to 81131a70a4)