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
- Make the root element of the ItemsPanelTemplate a Panel-derived type (StackPanel, WrapPanel, VirtualizingStackPanel, Grid, Canvas, DockPanel).
- Move wrapper properties (margin, background) onto the Panel itself instead of a Border wrapper.
- 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
- Never wrap the items panel in Border/Grid-like non-panel containers inside ItemsPanelTemplate.
- Root of ItemsPanelTemplate must be a Panel-derived type; put styling properties on the panel itself.
- Test custom ItemsPanelTemplate at first template application, not just at runtime data load.
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
- SR.Format(SR.ItemsPanelNotAPanel, templateHolder.RootType)
- SR.TemplateNotTargetType
- Animation_ChildMustBeKeyFrame
- Animation_ChildMustBeKeyFrame
- Animation_NoTextChildren
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 rulesView on GitHub (pinned to 81131a70a4)