From de33a22169044748448f4d7952ccb7f09eb9011c Mon Sep 17 00:00:00 2001 From: grokys Date: Mon, 9 Mar 2026 16:46:39 +0100 Subject: [PATCH] Adjust how selected content data context is set. Introduce a new `PART_SelectedContentDataContextHost` on which the `DataContext` will be set to be inherited by the selected tab content presenter(s). Fixes #18280 Fixes #20845 --- src/Avalonia.Controls/TabControl.cs | 33 ++++++++++--------- .../Controls/TabControl.xaml | 2 +- .../Controls/TabControl.xaml | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index f0c624489f..3a4df59395 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -32,6 +32,7 @@ namespace Avalonia.Controls private CompositeDisposable? _selectedItemSubscriptions; private ContentPresenter? _contentPart; private ContentPresenter? _contentPresenter2; + private Control? _dataContextHost; private int _previousSelectedIndex = -1; private CancellationTokenSource? _currentTransition; private bool _shouldAnimate; @@ -280,6 +281,7 @@ namespace Avalonia.Controls } container ??= ContainerFromIndex(SelectedIndex); + if (container != null) { if (SelectedContentTemplate != SelectContentTemplate(container.GetValue(ContentControl.ContentTemplateProperty))) @@ -295,6 +297,15 @@ namespace Avalonia.Controls bool isInitialFire = true; _selectedItemSubscriptions = new CompositeDisposable( + container.GetObservable(StyledElement.DataContextProperty).Subscribe(dc => + { + // The selected content presenter needs to inherit the DataContext of the TabItem, but + // the data context cannot be set directly on the ContentPresenter due to it calling + // ClearValue(DataContextProperty) in ContentPresenter.UpdateChild. For this reason we + // have a proxy element in the control template (PART_SelectedContentDataContextHost) + // which is used to set the DataContext inherited by the content presenters. + _dataContextHost?.DataContext = dc; + }), container.GetObservable(ContentControl.ContentProperty).Subscribe(content => { var contentElement = content as StyledElement; @@ -318,21 +329,7 @@ namespace Avalonia.Controls } else { - if (ContentPart != null) - { - ContentPart.Content = content; - // When ContentPart displays a Control, it doesn't set its - // DataContext to that of the Control's. If the content doesn't - // set a DataContext it gets inherited from the TabControl. - // Work around this by setting ContentPart's DataContext to - // the content's original DataContext (inherited from container). - if (contentElement is not null && - contentElement.DataContext != contentDataContext) - { - Debug.Assert(!contentElement.IsSet(DataContextProperty)); - ContentPart.DataContext = contentDataContext; - } - } + ContentPart?.Content = content; } isInitialFire = false; @@ -376,6 +373,12 @@ namespace Avalonia.Controls ItemsPresenterPart = e.NameScope.Find("PART_ItemsPresenter"); ItemsPresenterPart?.ApplyTemplate(); + _dataContextHost = e.NameScope.Find("PART_SelectedContentDataContextHost"); + + // Initialize the data context host with the data context of the selected tab, if any. + if (_dataContextHost is not null && ContainerFromIndex(SelectedIndex) is { } selectedTab) + _dataContextHost.DataContext = selectedTab.DataContext; + UpdateTabStripPlacement(); if (ItemsPresenterPart?.Panel is { } panel) diff --git a/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml b/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml index 123a1cff7d..75d6f7cd57 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml @@ -35,7 +35,7 @@ - + - +