Browse Source

Fixed SelectedContent and SelectedContentTemplate only updating when the selected tab changes

Flipped ContentTemplate priority to match WPF, where TabItem's template wins over TabControl's
Made various properties read-only DirectProperty
pull/11366/head
Tom Edwards 3 years ago
parent
commit
95cbd82144
  1. 62
      src/Avalonia.Controls/TabControl.cs
  2. 42
      src/Avalonia.Controls/TabItem.cs

62
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;
/// <summary>
/// Defines the <see cref="TabStripPlacement"/> property.
/// </summary>
@ -46,14 +51,14 @@ namespace Avalonia.Controls
/// <summary>
/// The selected content property
/// </summary>
public static readonly StyledProperty<object?> SelectedContentProperty =
AvaloniaProperty.Register<TabControl, object?>(nameof(SelectedContent));
public static readonly DirectProperty<TabControl, object?> SelectedContentProperty =
AvaloniaProperty.RegisterDirect<TabControl, object?>(nameof(SelectedContent), o => o.SelectedContent);
/// <summary>
/// The selected content template property
/// </summary>
public static readonly StyledProperty<IDataTemplate?> SelectedContentTemplateProperty =
AvaloniaProperty.Register<TabControl, IDataTemplate?>(nameof(SelectedContentTemplate));
public static readonly DirectProperty<TabControl, IDataTemplate?> SelectedContentTemplateProperty =
AvaloniaProperty.RegisterDirect<TabControl, IDataTemplate?>(nameof(SelectedContentTemplate), o => o.SelectedContentTemplate);
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
@ -115,11 +120,10 @@ namespace Avalonia.Controls
/// <value>
/// The content of the selected tab.
/// </value>
[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);
}
/// <summary>
@ -128,11 +132,10 @@ namespace Avalonia.Controls
/// <value>
/// The content template of the selected tab.
/// </value>
[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<IDataTemplate?>();
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)
{

42
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
{
/// <summary>
/// An item in a <see cref="TabStrip"/> or <see cref="TabControl"/>.
/// An item in a <see cref="TabControl"/>.
/// </summary>
[PseudoClasses(":pressed", ":selected")]
public class TabItem : HeaderedContentControl, ISelectable
{
private Dock? _tabStripPlacement;
private IDisposable? _ownerSubscriptions;
/// <summary>
/// Defines the <see cref="TabStripPlacement"/> property.
/// </summary>
public static readonly StyledProperty<Dock> TabStripPlacementProperty =
TabControl.TabStripPlacementProperty.AddOwner<TabItem>();
public static readonly DirectProperty<TabItem, Dock?> TabStripPlacementProperty =
AvaloniaProperty.RegisterDirect<TabItem, Dock?>(nameof(TabStripPlacement), o => o.TabStripPlacement);
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
@ -37,16 +43,12 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets the tab strip placement.
/// Gets the placement of this tab relative to the outer <see cref="TabControl"/>, if there is one.
/// </summary>
/// <value>
/// The tab strip placement.
/// </value>
[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);
}
/// <summary>
@ -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<TabControl>() 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)

Loading…
Cancel
Save