dotnet/wpf · error · InvalidOperationException

Microsoft.Windows.Controls.SR.Ribbon_ContextualTabHeadersSou…

Error message

Microsoft.Windows.Controls.SR.Ribbon_ContextualTabHeadersSourceInvalid

What it means

During Ribbon template application (OnApplyTemplate path), if the RibbonContextualTabGroupItemsControl was found but has no ItemsSource, the Ribbon refuses a configuration where both ContextualTabGroupsSource is set AND the manual ContextualTabGroups collection already contains items — the two ways of supplying contextual tab groups are mutually exclusive. It throws InvalidOperationException (Ribbon_ContextualTabHeadersSourceInvalid).

Solutions

  1. Remove manual items from ContextualTabGroups when using ContextualTabGroupsSource.
  2. Or remove the ContextualTabGroupsSource binding and populate ContextualTabGroups directly.
  3. Choose one population strategy (source binding OR direct items) per Ribbon instance.

Example fix

// before
<Ribbon ContextualTabGroupsSource="{Binding Tabs}">
  <Ribbon.ContextualTabGroups>
    <RibbonContextualTabGroup Header="Tools"/>
  </Ribbon.ContextualTabGroups>
</Ribbon>
// after
<Ribbon ContextualTabGroupsSource="{Binding Tabs}"/>
Defensive patterns

Strategy: validation

Validate before calling

bool ok = ribbon.ContextualTabGroupsSource == null || ribbon.ContextualTabGroups.Count == 0;
if (!ok) throw new InvalidOperationException("Use either ContextualTabGroupsSource or ContextualTabGroups, not both.");

Try / catch

try { ApplyTemplate(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("ContextualTab")) { ribbon.ContextualTabGroups.Clear(); ApplyTemplate(); }

Prevention

When it happens

Trigger: Setting ContextualTabGroupsSource (a binding to an external collection) while manually adding RibbonContextualTabGroup items to the ContextualTabGroups collection, then applying the template.

Common situations: Mixing XAML-declared contextual tab groups (<Ribbon.ContextualTabGroups>) with a ContextualTabGroupsSource binding, often after refactoring from declarative items to a source binding without clearing the collection.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/System.Windows.Controls.Ribbon/Microsoft/Windows/Controls/Ribbon/Ribbon.cs:752

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _itemsPresenter = GetTemplateChild("ItemsPresenter") as ItemsPresenter;
            _itemsPresenterPopup = this.GetTemplateChild(ItemsPresenterPopupTemplateName) as Popup;

            _tabHeaderItemsControl = this.GetTemplateChild("TabHeaderItemsControl") as RibbonTabHeaderItemsControl;
            if (_tabHeaderItemsControl != null && _tabHeaderItemsControl.ItemsSource == null)
            {
                _tabHeaderItemsControl.ItemsSource = _tabHeaderItemsSource;
            }

            _groupHeaderItemsControl = this.GetTemplateChild(Ribbon.ContextualTabGroupItemsControlTemplateName) as RibbonContextualTabGroupItemsControl;
            if (_groupHeaderItemsControl != null && _groupHeaderItemsControl.ItemsSource == null)
            {
                if (ContextualTabGroupsSource != null && ContextualTabGroups.Count > 0)
                {
                    throw new InvalidOperationException(Microsoft.Windows.Controls.SR.Ribbon_ContextualTabHeadersSourceInvalid);
                }
                if (ContextualTabGroupsSource != null)
                {
                    ContextualTabGroupItemsControl.ItemsSource = ContextualTabGroupsSource;
                }
                else if (ContextualTabGroups != null)
                {
                    ContextualTabGroupItemsControl.ItemsSource = ContextualTabGroups;
                }
            }

            this.RibbonTitlePanel = this.GetTemplateChild(Ribbon.TitlePanelTemplateName) as RibbonTitlePanel;
            _qatTopHost = this.GetTemplateChild(Ribbon.QatHostTemplateName) as UIElement;
            _titleHost = this.GetTemplateChild(Ribbon.TitleHostTemplateName) as UIElement;
            _helpPaneHost = this.GetTemplateChild(Ribbon.HelpPaneTemplateName) as UIElement;

            PropertyHelper.TransferProperty(this, ContextMenuProperty);   // Coerce to get a default ContextMenu if none has been specified.
            PropertyHelper.TransferProperty(this, RibbonControlService.CanAddToQuickAccessToolBarDirectlyProperty);

View on GitHub (pinned to 81131a70a4)