diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index b86624ead8..be5ab415b0 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -10,6 +10,7 @@ using Avalonia.LogicalTree; using Avalonia.VisualTree; using Avalonia.Automation; using Avalonia.Controls.Metadata; +using Avalonia.Reactive; namespace Avalonia.Controls { @@ -19,6 +20,10 @@ namespace Avalonia.Controls [TemplatePart("PART_ItemsPresenter", typeof(ItemsPresenter))] public class TabControl : SelectingItemsControl, IContentPresenterHost { + private object? _selectedContent; + private IDataTemplate? _selectedContentTemplate; + private CompositeDisposable? _selectedItemSubscriptions; + /// /// Defines the property. /// @@ -46,14 +51,14 @@ namespace Avalonia.Controls /// /// The selected content property /// - public static readonly StyledProperty SelectedContentProperty = - AvaloniaProperty.Register(nameof(SelectedContent)); + public static readonly DirectProperty SelectedContentProperty = + AvaloniaProperty.RegisterDirect(nameof(SelectedContent), o => o.SelectedContent); /// /// The selected content template property /// - public static readonly StyledProperty SelectedContentTemplateProperty = - AvaloniaProperty.Register(nameof(SelectedContentTemplate)); + public static readonly DirectProperty SelectedContentTemplateProperty = + AvaloniaProperty.RegisterDirect(nameof(SelectedContentTemplate), o => o.SelectedContentTemplate); /// /// The default value for the property. @@ -115,11 +120,10 @@ namespace Avalonia.Controls /// /// The content of the selected tab. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")] public object? SelectedContent { - get { return GetValue(SelectedContentProperty); } - internal set { SetValue(SelectedContentProperty, value); } + get => _selectedContent; + internal set => SetAndRaise(SelectedContentProperty, ref _selectedContent, value); } /// @@ -128,11 +132,10 @@ namespace Avalonia.Controls /// /// The content template of the selected tab. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")] public IDataTemplate? SelectedContentTemplate { - get { return GetValue(SelectedContentTemplateProperty); } - internal set { SetValue(SelectedContentTemplateProperty, value); } + get => _selectedContentTemplate; + internal set => SetAndRaise(SelectedContentTemplateProperty, ref _selectedContentTemplate, value); } internal ItemsPresenter? ItemsPresenterPart { get; private set; } @@ -161,18 +164,10 @@ namespace Avalonia.Controls protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index) { base.PrepareContainerForItemOverride(element, item, index); - - if (element is TabItem tabItem) - { - if (ContentTemplate is { } ct) - tabItem.ContentTemplate = ct; - tabItem.SetValue(TabStripPlacementProperty, TabStripPlacement); - } - if (index == SelectedIndex && element is ContentControl container) + if (index == SelectedIndex) { - SelectedContentTemplate = container.ContentTemplate; - SelectedContent = container.Content; + UpdateSelectedContent(element); } } @@ -192,18 +187,25 @@ namespace Avalonia.Controls UpdateSelectedContent(); } - private void UpdateSelectedContent() + private void UpdateSelectedContent(Control? container = null) { + _selectedItemSubscriptions?.Dispose(); + _selectedItemSubscriptions = null; + if (SelectedIndex == -1) { SelectedContent = SelectedContentTemplate = null; } else { - var container = SelectedItem as IContentControl ?? - ContainerFromIndex(SelectedIndex) as IContentControl; - SelectedContentTemplate = container?.ContentTemplate; - SelectedContent = container?.Content; + container ??= ContainerFromIndex(SelectedIndex); + if (container != null) + { + _selectedItemSubscriptions = new CompositeDisposable( + container.GetObservable(ContentControl.ContentProperty).Subscribe(v => SelectedContent = v), + // Note how we fall back to our own ContentTemplate if the container doesn't specify one + container.GetObservable(ContentControl.ContentTemplateProperty).Subscribe(v => SelectedContentTemplate = v ?? ContentTemplate)); + } } } @@ -285,6 +287,16 @@ namespace Avalonia.Controls { RefreshContainers(); } + else if (change.Property == ContentTemplateProperty) + { + var newTemplate = change.GetNewValue(); + if (SelectedContentTemplate != newTemplate && + ContainerFromIndex(SelectedIndex) is { } container && + container.GetValue(ContentControl.ContentTemplateProperty) == null) + { + SelectedContentTemplate = newTemplate; // See also UpdateSelectedContent + } + } else if (change.Property == KeyboardNavigation.TabOnceActiveElementProperty && ItemsPresenterPart?.Panel is { } panel) { diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 4068404952..7a3465b1a4 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -1,22 +1,28 @@ +using System; using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; +using Avalonia.Reactive; +using Avalonia.VisualTree; namespace Avalonia.Controls { /// - /// An item in a or . + /// An item in a . /// [PseudoClasses(":pressed", ":selected")] public class TabItem : HeaderedContentControl, ISelectable { + private Dock? _tabStripPlacement; + private IDisposable? _ownerSubscriptions; + /// /// Defines the property. /// - public static readonly StyledProperty TabStripPlacementProperty = - TabControl.TabStripPlacementProperty.AddOwner(); + public static readonly DirectProperty TabStripPlacementProperty = + AvaloniaProperty.RegisterDirect(nameof(TabStripPlacement), o => o.TabStripPlacement); /// /// Defines the property. @@ -37,16 +43,12 @@ namespace Avalonia.Controls } /// - /// Gets the tab strip placement. + /// Gets the placement of this tab relative to the outer , if there is one. /// - /// - /// The tab strip placement. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031", - Justification = "This property is supposed to be inherited only and settable on parent TabControl.")] - public Dock TabStripPlacement + public Dock? TabStripPlacement { - get { return GetValue(TabStripPlacementProperty); } + get => _tabStripPlacement; + private set => SetAndRaise(TabStripPlacementProperty, ref _tabStripPlacement, value); } /// @@ -60,6 +62,24 @@ namespace Avalonia.Controls protected override AutomationPeer OnCreateAutomationPeer() => new ListItemAutomationPeer(this); + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + _ownerSubscriptions?.Dispose(); + _ownerSubscriptions = null; + + if (this.FindAncestorOfType() is { } owner && owner.IndexFromContainer(this) != -1) + { + SubscribeToOwnerProperties(owner); + } + } + + protected void SubscribeToOwnerProperties(AvaloniaObject owner) + { + _ownerSubscriptions = owner.GetObservable(TabControl.TabStripPlacementProperty).Subscribe(v => TabStripPlacement = v); + } + private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) { if (Header == null)