dotnet/wpf · error · InvalidOperationException

SR.Format(SR.ItemsPanelNotAPanel, root.Type)

Error message

SR.Format(SR.ItemsPanelNotAPanel, root.Type)

What it means

ItemsPanelTemplate's VisualTree must be a single Panel (the items host). During template sealing, ProcessTemplateBeforeSeal checks typeof(Panel).IsAssignableFrom(root.Type) and throws InvalidOperationException when the template's root element is not a Panel (e.g. a Border or Grid nested incorrectly). WPF requires this because the ItemsPresenter must find a panel to lay out generated item containers.

Solutions

  1. Make the root element of the ItemsPanelTemplate a Panel-derived type (StackPanel, WrapPanel, VirtualizingStackPanel, Grid, Canvas, DockPanel).
  2. Move wrapper properties (margin, background) onto the Panel itself instead of a Border wrapper.
  3. If wrapper behavior is truly needed, use a Grid (a Panel) as root and attach decorations via panel properties or restructure the control template instead.

Example fix

<!-- before -->
<ItemsPanelTemplate>
  <Border Background="LightGray">
    <StackPanel/>
  </Border>
</ItemsPanelTemplate>
<!-- after -->
<ItemsPanelTemplate>
  <StackPanel Background="LightGray"/>
</ItemsPanelTemplate>
Defensive patterns

Strategy: validation

Validate before calling

var tpl = myItemsControl.ItemsPanel;
var content = tpl?.LoadContent();
if (content != null && content is not Panel)
    throw new InvalidOperationException($"ItemsPanelTemplate root must be a Panel, got {content.GetType().Name}");

Type guard

static bool IsValidItemsPanel(FrameworkElement root) => root is Panel;

Try / catch

try { element.ApplyTemplate(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("panel")) { /* fix template or fallback to default ItemsPanel */ }

Prevention

When it happens

Trigger: Defining an <ItemsPanelTemplate> whose root element is not a Panel (e.g. <Border>, <ContentControl>, or <TextBlock>) and applying it via ItemsControl.ItemsPanel or calling LoadContent/ApplyTemplate on such a template.

Common situations: Wrapping a StackPanel/VirtualizingStackPanel inside a <Border> inside ItemsPanelTemplate to try to add background or padding; copy-pasting a ControlTemplate into an ItemsPanelTemplate; migration from other frameworks where wrappers are common.

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/217a84c35949e687. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemsPanelTemplate.cs:125

            if( HasContent )
            {
                // This is a Baml-style template

                // Validate the root type (it must be a Panel)

                TemplateContent templateHolder = Template as TemplateContent;
                System.Xaml.XamlType panelType = templateHolder.SchemaContext.GetXamlType(typeof(Panel));
                if (templateHolder.RootType == null || !templateHolder.RootType.CanAssignTo(panelType))
                {
                    throw new InvalidOperationException(SR.Format(SR.ItemsPanelNotAPanel, templateHolder.RootType));
                }
            }

            else if ((root = this.VisualTree) != null)
            {
                // This is a FEF-style template
                if (!typeof(Panel).IsAssignableFrom(root.Type))
                    throw new InvalidOperationException(SR.Format(SR.ItemsPanelNotAPanel, root.Type));

                root.SetValue(Panel.IsItemsHostProperty, true);
            }
        }


        #endregion Internal Methods

        #region Protected Methods

        //-------------------------------------------------------------------
        //
        //  Protected Methods
        //
        //-------------------------------------------------------------------

        /// <summary>
        ///     Validate against the following rules

View on GitHub (pinned to 81131a70a4)