From 4f549c16fc35d527da751167b19467f5bd51918a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 31 Aug 2018 14:43:48 +0200 Subject: [PATCH 01/31] Initial --- samples/ControlCatalog/ControlCatalog.csproj | 15 ++ samples/ControlCatalog/MainView.xaml | 6 +- .../ControlCatalog/Pages/TabControlPage.xaml | 124 ++++++++++ .../Pages/TabControlPage.xaml.cs | 80 +++++++ samples/ControlCatalog/SideBar.xaml | 104 +++++---- src/Avalonia.Controls/ContentControl.cs | 4 +- .../Generators/TabItemContainerGenerator.cs | 60 +++++ .../Primitives/HeaderedContentControl.cs | 21 +- src/Avalonia.Controls/TabControl.cs | 176 ++++++++------ src/Avalonia.Controls/TabItem.cs | 73 ++++++ .../Avalonia.Themes.Default.csproj | 8 + src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 + src/Avalonia.Themes.Default/TabControl.xaml | 220 ++++++++++++++---- src/Avalonia.Themes.Default/TabItem.xaml | 45 ++++ .../TabControlTests.cs | 145 ++++++------ 15 files changed, 830 insertions(+), 252 deletions(-) create mode 100644 samples/ControlCatalog/Pages/TabControlPage.xaml create mode 100644 samples/ControlCatalog/Pages/TabControlPage.xaml.cs create mode 100644 src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs create mode 100644 src/Avalonia.Themes.Default/TabItem.xaml diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index dea9b35e24..8eab683049 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,6 +11,9 @@ + + + @@ -33,6 +36,18 @@ + + + + TabControlPage.xaml + + + + + + MSBuild:Compile + + \ No newline at end of file diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 87cb5e9c5c..1b613aee9a 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"> - - - @@ -21,12 +18,13 @@ - + + 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 7d72d1821b..dbab4e4a27 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -1,52 +1,64 @@ - + - + - + - + diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index 6da6da54a5..4621524bdc 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -45,7 +45,7 @@ namespace Avalonia.Controls static ContentControl() { ContentControlMixin.Attach(ContentProperty, x => x.LogicalChildren); - } + } /// /// Gets or sets the content to display. @@ -65,7 +65,7 @@ namespace Avalonia.Controls { get { return GetValue(ContentTemplateProperty); } set { SetValue(ContentTemplateProperty, value); } - } + } /// /// Gets the presenter from the control's template. diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs new file mode 100644 index 0000000000..75788e393f --- /dev/null +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -0,0 +1,60 @@ +// 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. + +namespace Avalonia.Controls.Generators +{ + using Avalonia.Controls.Primitives; + + 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) + { + if (tabItem.Header != headered.Header) + { + 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..b0517c23f1 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 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 70cf8b4e05..043242543b 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -1,10 +1,14 @@ // 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 System; + 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 +18,50 @@ 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 { Orientation = Orientation.Horizontal }); + + internal ItemsPresenter ItemsPresenterPart { get; private set; } + + internal ContentPresenter ContentPart { get; private set; } /// /// Initializes static members of the class. @@ -43,35 +69,26 @@ 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; - } - - /// - /// Gets or sets the transition to use when switching tabs. - /// - public IPageTransition PageTransition - { - get { return GetValue(PageTransitionProperty); } - set { SetValue(PageTransitionProperty, value); } + get { return GetValue(VerticalContentAlignmentProperty); } + set { SetValue(VerticalContentAlignmentProperty, value); } } /// @@ -83,67 +100,82 @@ namespace Avalonia.Controls set { SetValue(TabStripPlacementProperty, value); } } - protected override IItemContainerGenerator CreateItemContainerGenerator() + /// + /// Gets or sets the data template used to display the content of the control. + /// + public IDataTemplate ContentTemplate { - // 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; + get { return GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } } - protected override void OnTemplateApplied(TemplateAppliedEventArgs e) + /// + /// Gets or sets the currently selected content. + /// + /// + /// The content of the selected. + /// + public object SelectedContent { - base.OnTemplateApplied(e); - - TabStrip = e.NameScope.Find("PART_TabStrip"); - Pages = e.NameScope.Find("PART_Content"); + get { return GetValue(SelectedContentProperty); } + set { SetValue(SelectedContentProperty, value); } } /// - /// Selects the content of a tab item. + /// Gets or sets the template for the currently selected content. /// - /// The tab item. - /// The content. - private static object SelectContent(object o) + /// + /// The selected content template. + /// + public IDataTemplate SelectedContentTemplate + { + get { return GetValue(SelectedContentTemplateProperty); } + set { SetValue(SelectedContentTemplateProperty, value); } + } + + protected override IItemContainerGenerator CreateItemContainerGenerator() + { + return new TabItemContainerGenerator(this); + } + + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - var content = o as IContentControl; + base.OnTemplateApplied(e); + + ItemsPresenterPart = e.NameScope.Find("PART_ItemsPresenter"); - if (content != null) + if (ItemsPresenterPart == null) { - return content.Content; + throw new NotSupportedException("ItemsPresenter not found."); } - else + + ContentPart = e.NameScope.Find("PART_Content"); + + if (ContentPart == null) { - return o; - } + throw new NotSupportedException("ContentPresenter not found."); + } } - /// - /// Selects the header of a tab item. - /// - /// The tab item. - /// The content. - private static object SelectHeader(object o) + /// + protected override void OnGotFocus(GotFocusEventArgs e) { - var headered = o as IHeadered; - var control = o as IControl; + base.OnGotFocus(e); - if (headered != null) + if (e.NavigationMethod == NavigationMethod.Directional) { - return headered.Header ?? string.Empty; + e.Handled = UpdateSelectionFromEventSource(e.Source); } - 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 + } + + /// + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + base.OnPointerPressed(e); + + 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/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index eceafc2371..4e73afb755 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -2,6 +2,9 @@ netstandard2.0 + + + @@ -15,4 +18,9 @@ + + + MSBuild:Compile + + \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 2b9132ee56..7b8710866a 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..43265764ec 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -1,50 +1,172 @@ - - - - - - \ 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..311fb2b973 --- /dev/null +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + 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 From 63de9f3ddf60ce26f6eadf75f228c8f166012bcb Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 16:22:01 +0200 Subject: [PATCH 02/31] Rework default style --- src/Avalonia.Themes.Default/TabControl.xaml | 114 ++------------------ 1 file changed, 8 insertions(+), 106 deletions(-) diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index 43265764ec..1e5b5c8841 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -44,79 +44,10 @@ - + + From ab97dbb12c87f2188eebd8bd22add64c6464ef81 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 20:28:20 +0200 Subject: [PATCH 03/31] Selector rework --- samples/ControlCatalog/SideBar.xaml | 5 ++--- src/Avalonia.Themes.Default/TabItem.xaml | 16 +++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index dbab4e4a27..3552f224fc 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -39,7 +39,7 @@ - - - - - - - - - - - - From 00cc08f979b38625393bd87dd06fe0070fff1faa Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 23:44:04 +0200 Subject: [PATCH 04/31] New selectors --- samples/ControlCatalog/SideBar.xaml | 4 ++-- src/Avalonia.Themes.Default/TabItem.xaml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 3552f224fc..62c200d8d6 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -52,11 +52,11 @@ - - diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml index b51d1426ef..8b30d40d7e 100644 --- a/src/Avalonia.Themes.Default/TabItem.xaml +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -25,19 +25,19 @@ - - - - - From 8985af21ba685036740e8d75d3a8b57b46c0e53d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 7 Sep 2018 00:24:24 +0200 Subject: [PATCH 05/31] Selector fix --- samples/ControlCatalog/SideBar.xaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 62c200d8d6..3ec8e43b07 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -52,12 +52,16 @@ + + From fc709c3a0fc9b72b6eec7038b2be3adf8917c036 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 7 Sep 2018 00:51:25 +0200 Subject: [PATCH 06/31] Partially fix tab strip orientation. --- src/Avalonia.Controls/TabControl.cs | 2 +- src/Avalonia.Themes.Default/TabControl.xaml | 30 +++++++-------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 043242543b..f81e1560c2 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -57,7 +57,7 @@ namespace Avalonia.Controls /// The default value for the property. /// private static readonly FuncTemplate DefaultPanel = - new FuncTemplate(() => new WrapPanel { Orientation = Orientation.Horizontal }); + new FuncTemplate(() => new WrapPanel()); internal ItemsPresenter ItemsPresenterPart { get; private set; } diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index 1e5b5c8841..e551b2e841 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -24,7 +24,6 @@ + - - + From ed460795716b3df233fec43ed791a5a114ebf498 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 7 Sep 2018 01:30:49 +0200 Subject: [PATCH 07/31] Share the same style between TabItem and TabStripItem --- src/Avalonia.Themes.Default/TabItem.xaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml index 8b30d40d7e..6d7cdea1fd 100644 --- a/src/Avalonia.Themes.Default/TabItem.xaml +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -3,12 +3,9 @@ - - - - From 9d5b607707406e0124eac1b6a14694d171024440 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 7 Nov 2018 18:03:43 +0100 Subject: [PATCH 08/31] csproj fix --- src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index b408f2a0be..d854a136e9 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -25,4 +25,3 @@ - From e87967a4ad8e943a2214d74370786edd05ee8504 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 7 Nov 2018 18:14:21 +0100 Subject: [PATCH 09/31] Fix csproj --- samples/ControlCatalog/ControlCatalog.csproj | 14 +++----------- .../Avalonia.Themes.Default.csproj | 13 +++++-------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 13b79d75b5..4af6171a89 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,9 +11,7 @@ - - - + @@ -27,15 +25,9 @@ - - TabControlPage.xaml - - - - - + MSBuild:Compile - + diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index d854a136e9..af1899bab1 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -3,9 +3,6 @@ netstandard2.0 false - - - @@ -16,12 +13,12 @@ + + + + MSBuild:Compile + - - - MSBuild:Compile - - From e1fae9692df4a9ec143b0891e7b9ad1f811c72d8 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 7 Nov 2018 18:19:21 +0100 Subject: [PATCH 10/31] Remove extra ItemGroup --- samples/ControlCatalog/ControlCatalog.csproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 4af6171a89..7b30951d61 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -23,12 +23,6 @@ - - - - MSBuild:Compile - - From f8b196a39b6443ad537b40c99460256a10e7fb1f Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 12 Nov 2018 18:00:22 +0100 Subject: [PATCH 11/31] Minor fixes --- samples/ControlCatalog/ControlCatalog.csproj | 1 - src/Avalonia.Controls/ContentControl.cs | 4 +-- .../Generators/TabItemContainerGenerator.cs | 15 ++++------ .../Primitives/HeaderedContentControl.cs | 2 +- src/Avalonia.Controls/TabControl.cs | 28 ++++++------------- .../Avalonia.Themes.Default.csproj | 5 ---- src/Avalonia.Themes.Default/TabControl.xaml | 1 - 7 files changed, 17 insertions(+), 39 deletions(-) diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 7b30951d61..d0a746f87d 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,7 +11,6 @@ - diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index f5e2c14f39..20ec581108 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls { get { return GetValue(ContentTemplateProperty); } set { SetValue(ContentTemplateProperty, value); } - } + } /// /// Gets the presenter from the control's template. @@ -102,4 +102,4 @@ namespace Avalonia.Controls Presenter = presenter; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs index 75788e393f..088f9e30ea 100644 --- a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -1,10 +1,10 @@ // 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. -namespace Avalonia.Controls.Generators -{ - using Avalonia.Controls.Primitives; +using Avalonia.Controls.Primitives; +namespace Avalonia.Controls.Generators +{ public class TabItemContainerGenerator : ItemContainerGenerator { public TabItemContainerGenerator(TabControl owner) @@ -30,10 +30,7 @@ namespace Avalonia.Controls.Generators { if (item is IHeadered headered) { - if (tabItem.Header != headered.Header) - { - tabItem.Header = headered.Header; - } + tabItem.Header = headered.Header; } else { @@ -50,9 +47,9 @@ namespace Avalonia.Controls.Generators } 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 b0517c23f1..7a46e0f776 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs @@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets or sets the data template used to display the content of the control. + /// Gets or sets the data template used to display the header content of the control. /// public IDataTemplate HeaderTemplate { diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index f81e1560c2..bf57133120 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -1,8 +1,6 @@ // 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 System; - using Avalonia.Controls.Generators; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; @@ -92,7 +90,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the tabstrip placement of the tabcontrol. + /// Gets or sets the tabstrip placement of the TabControl. /// public Dock TabStripPlacement { @@ -101,7 +99,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the data template used to display the content of the control. + /// Gets or sets the default data template used to display the content of the selected tab. /// public IDataTemplate ContentTemplate { @@ -110,10 +108,10 @@ namespace Avalonia.Controls } /// - /// Gets or sets the currently selected content. + /// Gets or sets the content of the selected tab. /// /// - /// The content of the selected. + /// The content of the selected tab. /// public object SelectedContent { @@ -122,10 +120,10 @@ namespace Avalonia.Controls } /// - /// Gets or sets the template for the currently selected content. + /// Gets or sets the content template for the selected tab. /// /// - /// The selected content template. + /// The content template of the selected tab. /// public IDataTemplate SelectedContentTemplate { @@ -142,19 +140,9 @@ namespace Avalonia.Controls { base.OnTemplateApplied(e); - ItemsPresenterPart = e.NameScope.Find("PART_ItemsPresenter"); - - if (ItemsPresenterPart == null) - { - throw new NotSupportedException("ItemsPresenter not found."); - } + ItemsPresenterPart = e.NameScope.Get("PART_ItemsPresenter"); - ContentPart = e.NameScope.Find("PART_Content"); - - if (ContentPart == null) - { - throw new NotSupportedException("ContentPresenter not found."); - } + ContentPart = e.NameScope.Get("PART_Content"); } /// diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index af1899bab1..638aba4d69 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -13,11 +13,6 @@ - - - - MSBuild:Compile - diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index e551b2e841..969887b38e 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -2,7 +2,6 @@ diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index 4d1c4b1ab0..e94653a3d6 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -52,5 +52,8 @@ 10 12 16 + + 10 + 10 diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 2f0d48db27..e22a62ee7f 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -54,7 +54,7 @@ diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index e94653a3d6..c40305151e 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -53,7 +53,6 @@ 12 16 - 10 - 10 + 10 diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 982af43587..2cb8ce2d24 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -53,7 +53,7 @@