diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index ec3bf799b4..06f808b726 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -2,9 +2,6 @@ xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - - - @@ -29,5 +26,6 @@ + diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml new file mode 100644 index 0000000000..5b10e7d790 --- /dev/null +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + This is the first page in the TabControl. + + + + + + + + + + This is the second page in the TabControl. + + + + + + + + + You should not see this. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tab Placement: + + Left + Bottom + Right + Top + + + + + diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs new file mode 100644 index 0000000000..808d90a49c --- /dev/null +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -0,0 +1,80 @@ +using System; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; +using Avalonia.Platform; + +using ReactiveUI; + +namespace ControlCatalog.Pages +{ + using System.Collections.Generic; + + public class TabControlPage : UserControl + { + public TabControlPage() + { + InitializeComponent(); + + DataContext = new PageViewModel + { + Tabs = new[] + { + new TabItemViewModel + { + Header = "Arch", + Text = "This is the first templated tab page.", + Image = LoadBitmap("resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg?assembly=ControlCatalog"), + }, + new TabItemViewModel + { + Header = "Leaf", + Text = "This is the second templated tab page.", + Image = LoadBitmap("resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg?assembly=ControlCatalog"), + }, + new TabItemViewModel + { + Header = "Disabled", + Text = "You should not see this.", + IsEnabled = false, + }, + }, + TabPlacement = Dock.Top, + }; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private IBitmap LoadBitmap(string uri) + { + var assets = AvaloniaLocator.Current.GetService(); + return new Bitmap(assets.Open(new Uri(uri))); + } + + private class PageViewModel : ReactiveObject + { + private Dock _tabPlacement; + + public TabItemViewModel[] Tabs { get; set; } + + public Dock TabPlacement + { + get { return _tabPlacement; } + set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); } + } + } + + private class TabItemViewModel + { + public string Header { get; set; } + public string Text { get; set; } + public IBitmap Image { get; set; } + public bool IsEnabled { get; set; } = true; + } + } +} diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index cc3c31d13a..3ec8e43b07 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -1,52 +1,67 @@ - + - - - - - + + + + + diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs new file mode 100644 index 0000000000..088f9e30ea --- /dev/null +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -0,0 +1,57 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls.Primitives; + +namespace Avalonia.Controls.Generators +{ + public class TabItemContainerGenerator : ItemContainerGenerator + { + public TabItemContainerGenerator(TabControl owner) + : base(owner, ContentControl.ContentProperty, ContentControl.ContentTemplateProperty) + { + Owner = owner; + } + + public new TabControl Owner { get; } + + protected override IControl CreateContainer(object item) + { + var tabItem = (TabItem)base.CreateContainer(item); + + tabItem.ParentTabControl = Owner; + + if (tabItem.HeaderTemplate == null) + { + tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty]; + } + + if (tabItem.Header == null) + { + if (item is IHeadered headered) + { + tabItem.Header = headered.Header; + } + else + { + if (!(tabItem.DataContext is IControl)) + { + tabItem.Header = tabItem.DataContext; + } + } + } + + if (!(tabItem.Content is IControl)) + { + tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty]; + } + + if (tabItem.Content == null) + { + tabItem[~ContentControl.ContentProperty] = tabItem[~StyledElement.DataContextProperty]; + } + + return tabItem; + } + } +} diff --git a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs index d67ebfd489..7a46e0f776 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Controls.Templates; + namespace Avalonia.Controls.Primitives { /// @@ -12,7 +14,13 @@ namespace Avalonia.Controls.Primitives /// Defines the property. /// public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); + AvaloniaProperty.Register(nameof(Header)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty HeaderTemplateProperty = + AvaloniaProperty.Register(nameof(HeaderTemplate)); /// /// Gets or sets the header content. @@ -21,6 +29,15 @@ namespace Avalonia.Controls.Primitives { get { return GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } + } + + /// + /// Gets or sets the data template used to display the header content of the control. + /// + public IDataTemplate HeaderTemplate + { + get { return GetValue(HeaderTemplateProperty); } + set { SetValue(HeaderTemplateProperty, value); } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 3aae256858..8eaf166f57 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -1,10 +1,12 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Animation; using Avalonia.Controls.Generators; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; +using Avalonia.Input; +using Avalonia.Layout; namespace Avalonia.Controls { @@ -14,28 +16,46 @@ namespace Avalonia.Controls public class TabControl : SelectingItemsControl { /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty PageTransitionProperty = - Avalonia.Controls.Carousel.PageTransitionProperty.AddOwner(); + public static readonly StyledProperty TabStripPlacementProperty = + AvaloniaProperty.Register(nameof(TabStripPlacement), defaultValue: Dock.Top); /// - /// Defines an that selects the content of a . + /// Defines the property. /// - public static readonly IMemberSelector ContentSelector = - new FuncMemberSelector(SelectContent); + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); /// - /// Defines an that selects the header of a . + /// Defines the property. /// - public static readonly IMemberSelector HeaderSelector = - new FuncMemberSelector(SelectHeader); + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty TabStripPlacementProperty = - AvaloniaProperty.Register(nameof(TabStripPlacement), defaultValue: Dock.Top); + public static readonly StyledProperty ContentTemplateProperty = + ContentControl.ContentTemplateProperty.AddOwner(); + + /// + /// The selected content property + /// + public static readonly StyledProperty SelectedContentProperty = + AvaloniaProperty.Register(nameof(SelectedContent)); + + /// + /// The selected content template property + /// + public static readonly StyledProperty SelectedContentTemplateProperty = + AvaloniaProperty.Register(nameof(SelectedContentTemplate)); + + /// + /// The default value for the property. + /// + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new WrapPanel()); /// /// Initializes static members of the class. @@ -43,107 +63,107 @@ namespace Avalonia.Controls static TabControl() { SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); - FocusableProperty.OverrideDefaultValue(false); + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); AffectsMeasure(TabStripPlacementProperty); } /// - /// Gets the pages portion of the 's template. + /// Gets or sets the horizontal alignment of the content within the control. /// - public IControl Pages + public HorizontalAlignment HorizontalContentAlignment { - get; - private set; + get { return GetValue(HorizontalContentAlignmentProperty); } + set { SetValue(HorizontalContentAlignmentProperty, value); } } /// - /// Gets the tab strip portion of the 's template. + /// Gets or sets the vertical alignment of the content within the control. /// - public IControl TabStrip + public VerticalAlignment VerticalContentAlignment { - get; - private set; + get { return GetValue(VerticalContentAlignmentProperty); } + set { SetValue(VerticalContentAlignmentProperty, value); } } /// - /// Gets or sets the transition to use when switching tabs. + /// Gets or sets the tabstrip placement of the TabControl. /// - public IPageTransition PageTransition + public Dock TabStripPlacement { - get { return GetValue(PageTransitionProperty); } - set { SetValue(PageTransitionProperty, value); } + get { return GetValue(TabStripPlacementProperty); } + set { SetValue(TabStripPlacementProperty, value); } } /// - /// Gets or sets the tabstrip placement of the tabcontrol. + /// Gets or sets the default data template used to display the content of the selected tab. /// - public Dock TabStripPlacement + public IDataTemplate ContentTemplate { - get { return GetValue(TabStripPlacementProperty); } - set { SetValue(TabStripPlacementProperty, value); } + get { return GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } } + /// + /// Gets or sets the content of the selected tab. + /// + /// + /// The content of the selected tab. + /// + public object SelectedContent + { + get { return GetValue(SelectedContentProperty); } + internal set { SetValue(SelectedContentProperty, value); } + } + + /// + /// Gets or sets the content template for the selected tab. + /// + /// + /// The content template of the selected tab. + /// + public IDataTemplate SelectedContentTemplate + { + get { return GetValue(SelectedContentTemplateProperty); } + internal set { SetValue(SelectedContentTemplateProperty, value); } + } + + internal ItemsPresenter ItemsPresenterPart { get; private set; } + + internal ContentPresenter ContentPart { get; private set; } + protected override IItemContainerGenerator CreateItemContainerGenerator() { - // TabControl doesn't actually create items - instead its TabStrip and Carousel - // children create the items. However we want it to be a SelectingItemsControl - // so that it has the Items/SelectedItem etc properties. In this case, we can - // return a null ItemContainerGenerator to disable the creation of item containers. - return null; + return new TabItemContainerGenerator(this); } protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { base.OnTemplateApplied(e); - TabStrip = e.NameScope.Find("PART_TabStrip"); - Pages = e.NameScope.Find("PART_Content"); + ItemsPresenterPart = e.NameScope.Get("PART_ItemsPresenter"); + + ContentPart = e.NameScope.Get("PART_Content"); } - /// - /// Selects the content of a tab item. - /// - /// The tab item. - /// The content. - private static object SelectContent(object o) + /// + protected override void OnGotFocus(GotFocusEventArgs e) { - var content = o as IContentControl; + base.OnGotFocus(e); - if (content != null) + if (e.NavigationMethod == NavigationMethod.Directional) { - return content.Content; + e.Handled = UpdateSelectionFromEventSource(e.Source); } - else - { - return o; - } } - /// - /// Selects the header of a tab item. - /// - /// The tab item. - /// The content. - private static object SelectHeader(object o) + /// + protected override void OnPointerPressed(PointerPressedEventArgs e) { - var headered = o as IHeadered; - var control = o as IControl; + base.OnPointerPressed(e); - if (headered != null) - { - return headered.Header ?? string.Empty; - } - else if (control != null) - { - // Non-headered control items should result in TabStripItems with empty content. - // If a TabStrip is created with non IHeadered controls as its items, don't try to - // display the control in the TabStripItem: the content portion will also try to - // display this control, resulting in dual-parentage breakage. - return string.Empty; - } - else + if (e.MouseButton == MouseButton.Left) { - return o; + e.Handled = UpdateSelectionFromEventSource(e.Source); } } } diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 4fb68c8b6f..80a3846ab2 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -11,12 +11,20 @@ namespace Avalonia.Controls /// public class TabItem : HeaderedContentControl, ISelectable { + /// + /// Defines the property. + /// + public static readonly StyledProperty TabStripPlacementProperty = + TabControl.TabStripPlacementProperty.AddOwner(); + /// /// Defines the property. /// public static readonly StyledProperty IsSelectedProperty = ListBoxItem.IsSelectedProperty.AddOwner(); + private TabControl _parentTabControl; + /// /// Initializes static members of the class. /// @@ -24,6 +32,19 @@ namespace Avalonia.Controls { SelectableMixin.Attach(IsSelectedProperty); FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); + IsSelectedProperty.Changed.AddClassHandler(x => x.UpdateSelectedContent); + DataContextProperty.Changed.AddClassHandler(x => x.UpdateHeader); + } + + /// + /// Gets the tab strip placement. + /// + /// + /// The tab strip placement. + /// + public Dock TabStripPlacement + { + get { return GetValue(TabStripPlacementProperty); } } /// @@ -34,5 +55,57 @@ namespace Avalonia.Controls get { return GetValue(IsSelectedProperty); } set { SetValue(IsSelectedProperty, value); } } + + internal TabControl ParentTabControl + { + get => _parentTabControl; + set => _parentTabControl = value; + } + + private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) + { + if (Header == null) + { + if (obj.NewValue is IHeadered headered) + { + if (Header != headered.Header) + { + Header = headered.Header; + } + } + else + { + if (!(obj.NewValue is IControl)) + { + Header = obj.NewValue; + } + } + } + else + { + if (Header == obj.OldValue) + { + Header = obj.NewValue; + } + } + } + + private void UpdateSelectedContent(AvaloniaPropertyChangedEventArgs e) + { + if (!IsSelected) + { + return; + } + + if (ParentTabControl.SelectedContentTemplate != ContentTemplate) + { + ParentTabControl.SelectedContentTemplate = ContentTemplate; + } + + if (ParentTabControl.SelectedContent != Content) + { + ParentTabControl.SelectedContent = Content; + } + } } } diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 0bd91c8f1e..f45f48c260 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -30,6 +30,7 @@ + diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index 76cf190151..969887b38e 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -1,50 +1,63 @@ - - - - - - \ No newline at end of file + + + + + + + + diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml new file mode 100644 index 0000000000..6d7cdea1fd --- /dev/null +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 322c14c6bd..a5c3881d37 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests TabItem selected; var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = new[] { (selected = new TabItem @@ -61,7 +61,7 @@ namespace Avalonia.Controls.UnitTests var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = items, }; @@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = collection, }; @@ -147,7 +147,7 @@ namespace Avalonia.Controls.UnitTests }, Child = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = collection, } }; @@ -172,7 +172,7 @@ namespace Avalonia.Controls.UnitTests var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), DataContext = "Base", DataTemplates = { @@ -182,41 +182,39 @@ namespace Avalonia.Controls.UnitTests }; ApplyTemplate(target); - var carousel = (Carousel)target.Pages; - var container = (ContentPresenter)carousel.Presenter.Panel.Children.Single(); - container.UpdateChild(); - var dataContext = ((TextBlock)container.Child).DataContext; + target.ContentPart.UpdateChild(); + var dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal(items[0], dataContext); target.SelectedIndex = 1; - container = (ContentPresenter)carousel.Presenter.Panel.Children.Single(); - container.UpdateChild(); - dataContext = ((Button)container.Child).DataContext; + target.ContentPart.UpdateChild(); + dataContext = ((Button)target.ContentPart.Child).DataContext; Assert.Equal(items[1], dataContext); target.SelectedIndex = 2; - dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext; + target.ContentPart.UpdateChild(); + dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal("Base", dataContext); target.SelectedIndex = 3; - container = (ContentPresenter)carousel.Presenter.Panel.Children[0]; - container.UpdateChild(); - dataContext = ((TextBlock)container.Child).DataContext; + target.ContentPart.UpdateChild(); + dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal("Qux", dataContext); target.SelectedIndex = 4; - dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext; + target.ContentPart.UpdateChild(); + dataContext = target.ContentPart.DataContext; Assert.Equal("Base", dataContext); } /// - /// Non-headered control items should result in TabStripItems with empty content. + /// Non-headered control items should result in TabItems with empty header. /// /// - /// If a TabStrip is created with non IHeadered controls as its items, don't try to - /// display the control in the TabStripItem: if the TabStrip is part of a TabControl - /// then *that* will also try to display the control, resulting in dual-parentage + /// If a TabControl is created with non IHeadered controls as its items, don't try to + /// display the control in the header: if the control is part of the header then + /// *that* control would also end up in the content region, resulting in dual-parentage /// breakage. /// [Fact] @@ -230,18 +228,20 @@ namespace Avalonia.Controls.UnitTests var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = items, }; ApplyTemplate(target); - var result = target.TabStrip.GetLogicalChildren() - .OfType() - .Select(x => x.Content) + var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren(); + + var result = logicalChildren + .OfType() + .Select(x => x.Header) .ToList(); - Assert.Equal(new object[] { string.Empty, string.Empty }, result); + Assert.Equal(new object[] { null, null }, result); } [Fact] @@ -249,7 +249,7 @@ namespace Avalonia.Controls.UnitTests { TabControl target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = new[] { new TabItem { Header = "Foo" }, @@ -262,70 +262,61 @@ namespace Avalonia.Controls.UnitTests target.SelectedIndex = 2; - var carousel = (Carousel)target.Pages; - var page = (TabItem)carousel.SelectedItem; + var page = (TabItem)target.SelectedItem; Assert.Null(page.Content); } - private Control CreateTabControlTemplate(TabControl parent) + private IControlTemplate TabControlTemplate() { - return new StackPanel - { - Children = - { - new TabStrip - { - Name = "PART_TabStrip", - Template = new FuncControlTemplate(CreateTabStripTemplate), - MemberSelector = TabControl.HeaderSelector, - [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], - [!!TabStrip.SelectedIndexProperty] = parent[!!TabControl.SelectedIndexProperty] - }, - new Carousel - { - Name = "PART_Content", - Template = new FuncControlTemplate(CreateCarouselTemplate), - MemberSelector = TabControl.ContentSelector, - [!Carousel.ItemsProperty] = parent[!TabControl.ItemsProperty], - [!Carousel.SelectedItemProperty] = parent[!TabControl.SelectedItemProperty], - } - } - }; - } + return new FuncControlTemplate(parent => - private Control CreateTabStripTemplate(TabStrip parent) - { - return new ItemsPresenter - { - Name = "PART_ItemsPresenter", - [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], - [!CarouselPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty], - }; + new StackPanel + { + Children = { + new ItemsPresenter + { + Name = "PART_ItemsPresenter", + [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], + [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty], + }, + new ContentPresenter + { + Name = "PART_Content", + [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty], + [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty], + } + } + }); } - private Control CreateCarouselTemplate(Carousel control) + private IControlTemplate TabItemTemplate() { - return new CarouselPresenter - { - Name = "PART_ItemsPresenter", - [!CarouselPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty], - [!CarouselPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty], - [!CarouselPresenter.MemberSelectorProperty] = control[!ItemsControl.MemberSelectorProperty], - [!CarouselPresenter.SelectedIndexProperty] = control[!SelectingItemsControl.SelectedIndexProperty], - [~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty], - }; + return new FuncControlTemplate(parent => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty], + [!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty] + }); } private void ApplyTemplate(TabControl target) { target.ApplyTemplate(); - var carousel = (Carousel)target.Pages; - carousel.ApplyTemplate(); - carousel.Presenter.ApplyTemplate(); - var tabStrip = (TabStrip)target.TabStrip; - tabStrip.ApplyTemplate(); - tabStrip.Presenter.ApplyTemplate(); + + target.Presenter.ApplyTemplate(); + + foreach (var tabItem in target.GetLogicalChildren().OfType()) + { + tabItem.Template = TabItemTemplate(); + + tabItem.ApplyTemplate(); + + ((ContentPresenter)tabItem.Presenter).UpdateChild(); + } + + target.ContentPart.ApplyTemplate(); } private class Item