dotnet/wpf · error · InvalidOperationException

SR.Format(SR.TemplateMustBeFE, new object[] )

Error message

SR.Format(SR.TemplateMustBeFE, new object[] { rootObject.GetType().FullName })

What it means

WireRootObjectToParent requires the root object of a template's content to be a UIElement when the container is a FrameworkElement (so it can be assigned to TemplateChild). If the template's root XAML object is not a UIElement (e.g. a plain ContentElement or non-visual object), InvalidOperationException is thrown naming the root type.

Solutions

  1. Make the template's root element a UIElement-derived type (e.g. Grid, Border, StackPanel).
  2. Wrap non-visual content inside a visual container before returning it from the template.
  3. If content should be non-visual, ensure the template is applied in a content (not visual) position, or use a ContentPresenter.

Example fix

<!-- before -->
<ControlTemplate TargetType="Button"><Run Text="hi"/></ControlTemplate>
<!-- after -->
<ControlTemplate TargetType="Button"><TextBlock Text="hi"/></ControlTemplate>
Defensive patterns

Strategy: validation

Validate before calling

if (!(rootObject is UIElement))
    throw new InvalidOperationException($"Template root must be a UIElement, got {rootObject.GetType()}");

Type guard

static bool IsValidTemplateRoot(object o) => o is UIElement;

Try / catch

try { ApplyTemplate(t); } catch (InvalidOperationException ex) when (ex.Message.Contains("UIElement")) { /* fix template XAML */ }

Prevention

When it happens

Trigger: Defining a ControlTemplate/DataTemplate whose top-level element is not a UIElement (e.g. a Run, TextElement, or arbitrary CLR object) while the templated parent is a FrameworkElement.

Common situations: Hand-written or tool-mangled XAML where the root tag is wrong; wrapping content in a non-visual element by mistake; misunderstanding that template roots must be visual elements.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/FrameworkTemplate.cs:1122

                }

                InvalidatePropertiesOnTemplate(container, createdObject);
            }
        }

        private static DependencyObject WireRootObjectToParent(object createdObject, DependencyObject rootObject, DependencyObject container, FrameworkElement feContainer, INameScope nameScope)
        {
            rootObject = createdObject as DependencyObject;
            if (rootObject != null)
            {
                // Add the root to the appropriate tree.
                if (feContainer != null)
                {
                    // Put the root object into FE.Templatechild (must be a UIElement).
                    UIElement rootElement = rootObject as UIElement;
                    if (rootElement == null)
                    {
                        throw new InvalidOperationException(SR.Format(SR.TemplateMustBeFE, new object[] { rootObject.GetType().FullName }));
                    }
                    feContainer.TemplateChild = rootElement;

                    Debug.Assert(!(rootElement is FrameworkElement) ||
                        ((FrameworkElement)rootElement).TemplateChildIndex != -1);
                }
                // If we have a container that is not a FE, add to the logical tree of the FEF
                else if (container != null)
                {
                    FrameworkElement feResult;
                    FrameworkContentElement fceResult;
                    Helper.DowncastToFEorFCE(rootObject, out feResult, out fceResult, true);
                    FrameworkElementFactory.AddNodeToLogicalTree((FrameworkContentElement)container,
                        rootObject.GetType(), feResult != null, feResult, fceResult);
                }


                // Set the TemplateNameScope on the root

View on GitHub (pinned to 81131a70a4)