Browse Source

Initial

pull/1842/head
Benedikt Schroeder 8 years ago
parent
commit
4f549c16fc
  1. 15
      samples/ControlCatalog/ControlCatalog.csproj
  2. 6
      samples/ControlCatalog/MainView.xaml
  3. 124
      samples/ControlCatalog/Pages/TabControlPage.xaml
  4. 80
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  5. 104
      samples/ControlCatalog/SideBar.xaml
  6. 4
      src/Avalonia.Controls/ContentControl.cs
  7. 60
      src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
  8. 21
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
  9. 176
      src/Avalonia.Controls/TabControl.cs
  10. 73
      src/Avalonia.Controls/TabItem.cs
  11. 8
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  12. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  13. 220
      src/Avalonia.Themes.Default/TabControl.xaml
  14. 45
      src/Avalonia.Themes.Default/TabItem.xaml
  15. 145
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs

15
samples/ControlCatalog/ControlCatalog.csproj

@ -11,6 +11,9 @@
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Assets\*" /> <EmbeddedResource Include="Assets\*" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="Pages\TabControlPage.xaml" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" /> <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" /> <EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
@ -33,6 +36,18 @@
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Pages\TabControlPage.xaml.cs">
<DependentUpon>TabControlPage.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Pages\TabControlPage.xaml">
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
</ItemGroup>
<Import Project="..\..\build\Serilog.props" /> <Import Project="..\..\build\Serilog.props" />
</Project> </Project>

6
samples/ControlCatalog/MainView.xaml

@ -2,9 +2,6 @@
xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabControl Classes="sidebar" Name="Sidebar"> <TabControl Classes="sidebar" Name="Sidebar">
<TabControl.PageTransition>
<CrossFade Duration="0.25"/>
</TabControl.PageTransition>
<TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem> <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
<TabItem Header="Border"><pages:BorderPage/></TabItem> <TabItem Header="Border"><pages:BorderPage/></TabItem>
<TabItem Header="Button"><pages:ButtonPage/></TabItem> <TabItem Header="Button"><pages:ButtonPage/></TabItem>
@ -21,12 +18,13 @@
<TabItem Header="Image"><pages:ImagePage/></TabItem> <TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem> <TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem> <TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem> <TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem> <TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem> <TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem> <TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem> <TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem> <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem> <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
</TabControl> </TabControl>
</UserControl> </UserControl>

124
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -0,0 +1,124 @@
<UserControl xmlns="https://github.com/avaloniaui">
<DockPanel>
<TextBlock
DockPanel.Dock="Top"
Classes="h1"
Text="TabControl"
Margin="4">
</TextBlock>
<TextBlock
DockPanel.Dock="Top"
Classes="h2"
Text="A tab control that displays a tab strip along with the content of the selected tab"
Margin="4">
</TextBlock>
<Grid
ColumnDefinitions="*,*"
RowDefinitions="*,100">
<DockPanel
Grid.Column="0"
Margin="4">
<TextBlock
DockPanel.Dock="Top"
Classes="h1"
Text="From Inline TabItems">
</TextBlock>
<TabControl
Margin="0 16"
TabStripPlacement="{Binding TabPlacement}">
<TabItem>
<TabItem.Header>
<TextBlock
Text="Arch"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="8">
</TextBlock>
</TabItem.Header>
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock>This is the first page in the TabControl.</TextBlock>
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg" Width="300"/>
</StackPanel>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock
Text="Leaf"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="8">
</TextBlock>
</TabItem.Header>
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock>This is the second page in the TabControl.</TextBlock>
<Image Source="resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg" Width="300"/>
</StackPanel>
</TabItem>
<TabItem IsEnabled="False">
<TabItem.Header>
<TextBlock
Text="Disabled"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="8">
</TextBlock>
</TabItem.Header>
<TextBlock>You should not see this.</TextBlock>
</TabItem>
</TabControl>
</DockPanel>
<DockPanel
Grid.Column="1"
Margin="4">
<TextBlock
DockPanel.Dock="Top"
Classes="h1"
Text="From DataTemplate">
</TextBlock>
<TabControl
Items="{Binding Tabs}"
Margin="0 16"
TabStripPlacement="{Binding TabPlacement}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Header}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="8">
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock Text="{Binding Text}"/>
<Image Source="{Binding Image}" Width="300"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.Styles>
<Style Selector="TabItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
</TabControl.Styles>
</TabControl>
</DockPanel>
<StackPanel
Grid.Row="1"
Grid.ColumnSpan="2"
Orientation="Horizontal"
Spacing="8"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Tab Placement:</TextBlock>
<DropDown SelectedIndex="{Binding TabPlacement, Mode=TwoWay}">
<DropDownItem>Left</DropDownItem>
<DropDownItem>Bottom</DropDownItem>
<DropDownItem>Right</DropDownItem>
<DropDownItem>Top</DropDownItem>
</DropDown>
</StackPanel>
</Grid>
</DockPanel>
</UserControl>

80
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<IAssetLoader>();
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;
}
}
}

104
samples/ControlCatalog/SideBar.xaml

@ -1,52 +1,64 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Style Selector="TabControl.sidebar"> <Style Selector="TabControl.sidebar">
<Setter Property="Template"> <Setter Property="TabStripPlacement" Value="Left"/>
<ControlTemplate> <Setter Property="Padding" Value="8 0 0 0"/>
<DockPanel> <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
<ScrollViewer MinWidth="190" Background="{DynamicResource ThemeAccentBrush}" DockPanel.Dock="Left"> <Setter Property="Template">
<TabStrip Name="PART_TabStrip" <ControlTemplate>
MemberSelector="{x:Static TabControl.HeaderSelector}" <Border
Items="{TemplateBinding Items}" Margin="{TemplateBinding Margin}"
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"> BorderBrush="{TemplateBinding BorderBrush}"
<TabStrip.ItemsPanel> BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPanelTemplate> <DockPanel>
<StackPanel Orientation="Vertical"/> <ScrollViewer
</ItemsPanelTemplate> Name="PART_ScrollViewer"
</TabStrip.ItemsPanel> HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
</TabStrip> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
</ScrollViewer> Background="{TemplateBinding Background}">
<Carousel Name="PART_Content" <ItemsPresenter
Margin="8 0 0 0" Name="PART_ItemsPresenter"
MemberSelector="{x:Static TabControl.ContentSelector}" MinWidth="190"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding SelectedIndex}" ItemsPanel="{TemplateBinding ItemsPanel}"
PageTransition="{TemplateBinding PageTransition}" ItemTemplate="{TemplateBinding ItemTemplate}"
Grid.Row="1"/> MemberSelector="{TemplateBinding MemberSelector}">
</DockPanel> </ItemsPresenter>
</ControlTemplate> </ScrollViewer>
</Setter> <ContentPresenter
</Style> Name="PART_Content"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabControl.sidebar TabStripItem"> <Style Selector="TabControl.sidebar > TabItem">
<Setter Property="Foreground" Value="White"/> <Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/> <Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0"/> <Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="16"/> <Setter Property="Margin" Value="0"/>
<Setter Property="Opacity" Value="0.5"/> <Setter Property="Padding" Value="16"/>
<Setter Property="Transitions"> <Setter Property="Opacity" Value="0.5"/>
<Transitions> <Setter Property="Transitions">
<DoubleTransition Property="Opacity" Duration="0:0:0.5"/> <Transitions>
</Transitions> <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
</Setter> </Transitions>
</Style> </Setter>
</Style>
<Style Selector="TabControl.sidebar TabStripItem:pointerover"> <Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Opacity" Value="1"/> <Setter Property="Opacity" Value="1"/>
</Style> </Style>
<Style Selector="TabControl.sidebar TabStripItem:selected"> <Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/> <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
<Setter Property="Opacity" Value="1"/> <Setter Property="Opacity" Value="1"/>
</Style> </Style>
</Styles> </Styles>

4
src/Avalonia.Controls/ContentControl.cs

@ -45,7 +45,7 @@ namespace Avalonia.Controls
static ContentControl() static ContentControl()
{ {
ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren); ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren);
} }
/// <summary> /// <summary>
/// Gets or sets the content to display. /// Gets or sets the content to display.
@ -65,7 +65,7 @@ namespace Avalonia.Controls
{ {
get { return GetValue(ContentTemplateProperty); } get { return GetValue(ContentTemplateProperty); }
set { SetValue(ContentTemplateProperty, value); } set { SetValue(ContentTemplateProperty, value); }
} }
/// <summary> /// <summary>
/// Gets the presenter from the control's template. /// Gets the presenter from the control's template.

60
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<TabItem>
{
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;
}
}
}

21
src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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 namespace Avalonia.Controls.Primitives
{ {
/// <summary> /// <summary>
@ -12,7 +14,13 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="Header"/> property. /// Defines the <see cref="Header"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<object> HeaderProperty = public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<ContentControl, object>(nameof(Header)); AvaloniaProperty.Register<HeaderedContentControl, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(HeaderTemplate));
/// <summary> /// <summary>
/// Gets or sets the header content. /// Gets or sets the header content.
@ -21,6 +29,15 @@ namespace Avalonia.Controls.Primitives
{ {
get { return GetValue(HeaderProperty); } get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); } set { SetValue(HeaderProperty, value); }
}
/// <summary>
/// Gets or sets the data template used to display the content of the control.
/// </summary>
public IDataTemplate HeaderTemplate
{
get { return GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
} }
} }
} }

176
src/Avalonia.Controls/TabControl.cs

@ -1,10 +1,14 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -14,28 +18,50 @@ namespace Avalonia.Controls
public class TabControl : SelectingItemsControl public class TabControl : SelectingItemsControl
{ {
/// <summary> /// <summary>
/// Defines the <see cref="PageTransition"/> property. /// Defines the <see cref="TabStripPlacement"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IPageTransition> PageTransitionProperty = public static readonly StyledProperty<Dock> TabStripPlacementProperty =
Avalonia.Controls.Carousel.PageTransitionProperty.AddOwner<TabControl>(); AvaloniaProperty.Register<TabControl, Dock>(nameof(TabStripPlacement), defaultValue: Dock.Top);
/// <summary> /// <summary>
/// Defines an <see cref="IMemberSelector"/> that selects the content of a <see cref="TabItem"/>. /// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary> /// </summary>
public static readonly IMemberSelector ContentSelector = public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
new FuncMemberSelector<object, object>(SelectContent); ContentControl.HorizontalContentAlignmentProperty.AddOwner<TabControl>();
/// <summary> /// <summary>
/// Defines an <see cref="IMemberSelector"/> that selects the header of a <see cref="TabItem"/>. /// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary> /// </summary>
public static readonly IMemberSelector HeaderSelector = public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
new FuncMemberSelector<object, object>(SelectHeader); ContentControl.VerticalContentAlignmentProperty.AddOwner<TabControl>();
/// <summary> /// <summary>
/// Defines the <see cref="TabStripPlacement"/> property. /// Defines the <see cref="ContentTemplate"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Dock> TabStripPlacementProperty = public static readonly StyledProperty<IDataTemplate> ContentTemplateProperty =
AvaloniaProperty.Register<TabControl, Dock>(nameof(TabStripPlacement), defaultValue: Dock.Top); ContentControl.ContentTemplateProperty.AddOwner<TabControl>();
/// <summary>
/// The selected content property
/// </summary>
public static readonly StyledProperty<object> SelectedContentProperty =
AvaloniaProperty.Register<TabControl, object>(nameof(SelectedContent));
/// <summary>
/// The selected content template property
/// </summary>
public static readonly StyledProperty<IDataTemplate> SelectedContentTemplateProperty =
AvaloniaProperty.Register<TabControl, IDataTemplate>(nameof(SelectedContentTemplate));
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new WrapPanel { Orientation = Orientation.Horizontal });
internal ItemsPresenter ItemsPresenterPart { get; private set; }
internal ContentPresenter ContentPart { get; private set; }
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TabControl"/> class. /// Initializes static members of the <see cref="TabControl"/> class.
@ -43,35 +69,26 @@ namespace Avalonia.Controls
static TabControl() static TabControl()
{ {
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected); SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
FocusableProperty.OverrideDefaultValue<TabControl>(false); ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure(TabStripPlacementProperty); AffectsMeasure(TabStripPlacementProperty);
} }
/// <summary> /// <summary>
/// Gets the pages portion of the <see cref="TabControl"/>'s template. /// Gets or sets the horizontal alignment of the content within the control.
/// </summary> /// </summary>
public IControl Pages public HorizontalAlignment HorizontalContentAlignment
{ {
get; get { return GetValue(HorizontalContentAlignmentProperty); }
private set; set { SetValue(HorizontalContentAlignmentProperty, value); }
} }
/// <summary> /// <summary>
/// Gets the tab strip portion of the <see cref="TabControl"/>'s template. /// Gets or sets the vertical alignment of the content within the control.
/// </summary> /// </summary>
public IControl TabStrip public VerticalAlignment VerticalContentAlignment
{ {
get; get { return GetValue(VerticalContentAlignmentProperty); }
private set; set { SetValue(VerticalContentAlignmentProperty, value); }
}
/// <summary>
/// Gets or sets the transition to use when switching tabs.
/// </summary>
public IPageTransition PageTransition
{
get { return GetValue(PageTransitionProperty); }
set { SetValue(PageTransitionProperty, value); }
} }
/// <summary> /// <summary>
@ -83,67 +100,82 @@ namespace Avalonia.Controls
set { SetValue(TabStripPlacementProperty, value); } set { SetValue(TabStripPlacementProperty, value); }
} }
protected override IItemContainerGenerator CreateItemContainerGenerator() /// <summary>
/// Gets or sets the data template used to display the content of the control.
/// </summary>
public IDataTemplate ContentTemplate
{ {
// TabControl doesn't actually create items - instead its TabStrip and Carousel get { return GetValue(ContentTemplateProperty); }
// children create the items. However we want it to be a SelectingItemsControl set { SetValue(ContentTemplateProperty, value); }
// 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;
} }
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) /// <summary>
/// Gets or sets the currently selected content.
/// </summary>
/// <value>
/// The content of the selected.
/// </value>
public object SelectedContent
{ {
base.OnTemplateApplied(e); get { return GetValue(SelectedContentProperty); }
set { SetValue(SelectedContentProperty, value); }
TabStrip = e.NameScope.Find<IControl>("PART_TabStrip");
Pages = e.NameScope.Find<IControl>("PART_Content");
} }
/// <summary> /// <summary>
/// Selects the content of a tab item. /// Gets or sets the template for the currently selected content.
/// </summary> /// </summary>
/// <param name="o">The tab item.</param> /// <value>
/// <returns>The content.</returns> /// The selected content template.
private static object SelectContent(object o) /// </value>
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<ItemsPresenter>("PART_ItemsPresenter");
if (content != null) if (ItemsPresenterPart == null)
{ {
return content.Content; throw new NotSupportedException("ItemsPresenter not found.");
} }
else
ContentPart = e.NameScope.Find<ContentPresenter>("PART_Content");
if (ContentPart == null)
{ {
return o; throw new NotSupportedException("ContentPresenter not found.");
} }
} }
/// <summary> /// <inheritdoc/>
/// Selects the header of a tab item. protected override void OnGotFocus(GotFocusEventArgs e)
/// </summary>
/// <param name="o">The tab item.</param>
/// <returns>The content.</returns>
private static object SelectHeader(object o)
{ {
var headered = o as IHeadered; base.OnGotFocus(e);
var control = o as IControl;
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. /// <inheritdoc/>
// If a TabStrip is created with non IHeadered controls as its items, don't try to protected override void OnPointerPressed(PointerPressedEventArgs e)
// display the control in the TabStripItem: the content portion will also try to {
// display this control, resulting in dual-parentage breakage. base.OnPointerPressed(e);
return string.Empty;
} if (e.MouseButton == MouseButton.Left)
else
{ {
return o; e.Handled = UpdateSelectionFromEventSource(e.Source);
} }
} }
} }

73
src/Avalonia.Controls/TabItem.cs

@ -11,12 +11,20 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class TabItem : HeaderedContentControl, ISelectable public class TabItem : HeaderedContentControl, ISelectable
{ {
/// <summary>
/// Defines the <see cref="TabStripPlacement"/> property.
/// </summary>
public static readonly StyledProperty<Dock> TabStripPlacementProperty =
TabControl.TabStripPlacementProperty.AddOwner<TabItem>();
/// <summary> /// <summary>
/// Defines the <see cref="IsSelected"/> property. /// Defines the <see cref="IsSelected"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsSelectedProperty = public static readonly StyledProperty<bool> IsSelectedProperty =
ListBoxItem.IsSelectedProperty.AddOwner<TabItem>(); ListBoxItem.IsSelectedProperty.AddOwner<TabItem>();
private TabControl _parentTabControl;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TabItem"/> class. /// Initializes static members of the <see cref="TabItem"/> class.
/// </summary> /// </summary>
@ -24,6 +32,19 @@ namespace Avalonia.Controls
{ {
SelectableMixin.Attach<TabItem>(IsSelectedProperty); SelectableMixin.Attach<TabItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); FocusableProperty.OverrideDefaultValue(typeof(TabItem), true);
IsSelectedProperty.Changed.AddClassHandler<TabItem>(x => x.UpdateSelectedContent);
DataContextProperty.Changed.AddClassHandler<TabItem>(x => x.UpdateHeader);
}
/// <summary>
/// Gets the tab strip placement.
/// </summary>
/// <value>
/// The tab strip placement.
/// </value>
public Dock TabStripPlacement
{
get { return GetValue(TabStripPlacementProperty); }
} }
/// <summary> /// <summary>
@ -34,5 +55,57 @@ namespace Avalonia.Controls
get { return GetValue(IsSelectedProperty); } get { return GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); } 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;
}
}
} }
} }

8
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@ -2,6 +2,9 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="TabItem.xaml" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> <ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" /> <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
@ -15,4 +18,9 @@
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\EmbedXaml.props" /> <Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<ItemGroup>
<EmbeddedResource Update="TabItem.xaml">
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
</ItemGroup>
</Project> </Project>

1
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -30,6 +30,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.TabStripItem.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TabStripItem.xaml?assembly=Avalonia.Themes.Default"/>
<!-- TabControl needs to come after TabStrip as it redefines the inner TabStrip.ItemsPanel--> <!-- TabControl needs to come after TabStrip as it redefines the inner TabStrip.ItemsPanel-->
<StyleInclude Source="resm:Avalonia.Themes.Default.TabControl.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TabControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TabItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TextBox.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TextBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ToggleButton.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.ToggleButton.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Expander.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.Expander.xaml?assembly=Avalonia.Themes.Default"/>

220
src/Avalonia.Themes.Default/TabControl.xaml

@ -1,50 +1,172 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TabControl"> <Style Selector="TabControl">
<Setter Property="Template"> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<ControlTemplate> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Border Background="{TemplateBinding Background}" <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
BorderBrush="{TemplateBinding BorderBrush}" <Setter Property="Padding" Value="4"/>
BorderThickness="{TemplateBinding BorderThickness}"> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<DockPanel> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<TabStrip Name="PART_TabStrip" <Setter Property="VerticalContentAlignment" Value="Stretch"/>
MemberSelector="{x:Static TabControl.HeaderSelector}" <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
Items="{TemplateBinding Items}" <Setter Property="Template">
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"/> <ControlTemplate>
<Carousel Name="PART_Content" <Border
MemberSelector="{x:Static TabControl.ContentSelector}" Margin="{TemplateBinding Margin}"
Items="{TemplateBinding Items}" BorderBrush="{TemplateBinding BorderBrush}"
SelectedIndex="{TemplateBinding SelectedIndex}" BorderThickness="{TemplateBinding BorderThickness}"
PageTransition="{TemplateBinding PageTransition}" Background="{TemplateBinding Background}"
Grid.Row="1"/> HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
</DockPanel> VerticalAlignment="{TemplateBinding VerticalAlignment}">
</Border> <ScrollViewer
</ControlTemplate> Name="PART_ScrollViewer"
</Setter> HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
</Style> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<Style Selector="TabControl[TabStripPlacement=Top] /template/ TabStrip"> <DockPanel>
<Setter Property="DockPanel.Dock" Value="Top"/> <ItemsPresenter
</Style> Name="PART_ItemsPresenter"
<Style Selector="TabControl[TabStripPlacement=Bottom] /template/ TabStrip"> DockPanel.Dock="Top"
<Setter Property="DockPanel.Dock" Value="Bottom"/> Items="{TemplateBinding Items}"
</Style> ItemsPanel="{TemplateBinding ItemsPanel}"
<Style Selector="TabControl[TabStripPlacement=Left] /template/ TabStrip"> ItemTemplate="{TemplateBinding ItemTemplate}"
<Setter Property="DockPanel.Dock" Value="Left"/> MemberSelector="{TemplateBinding MemberSelector}" >
<Setter Property="ItemsPanel"> </ItemsPresenter>
<Setter.Value> <ContentPresenter
<ItemsPanelTemplate> Name="PART_Content"
<StackPanel Orientation="Vertical"/> Margin="{TemplateBinding Padding}"
</ItemsPanelTemplate> HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
</Setter.Value> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
</Setter> Content="{TemplateBinding SelectedContent}"
</Style> ContentTemplate="{TemplateBinding SelectedContentTemplate}">
<Style Selector="TabControl[TabStripPlacement=Right] /template/ TabStrip"> </ContentPresenter>
<Setter Property="DockPanel.Dock" Value="Right"/> </DockPanel>
<Setter Property="ItemsPanel"> </ScrollViewer>
<Setter.Value> </Border>
<ItemsPanelTemplate> </ControlTemplate>
<StackPanel Orientation="Vertical"/> </Setter>
</ItemsPanelTemplate> </Style>
</Setter.Value> <Style Selector="TabControl[TabStripPlacement=Bottom]">
</Setter> <Setter Property="Template">
</Style> <ControlTemplate>
</Styles> <Border
Margin="{TemplateBinding Margin}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<ScrollViewer
Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<DockPanel>
<ItemsPresenter
Name="PART_ItemsPresenter"
DockPanel.Dock="Bottom"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}" >
</ItemsPresenter>
<ContentPresenter
Name="PART_Content"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabControl[TabStripPlacement=Left]">
<Setter Property="Template">
<ControlTemplate>
<Border
Margin="{TemplateBinding Margin}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<ScrollViewer
Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<DockPanel>
<ItemsPresenter
Name="PART_ItemsPresenter"
DockPanel.Dock="Left"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}" >
</ItemsPresenter>
<ContentPresenter
Name="PART_Content"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style Selector="TabControl[TabStripPlacement=Right]">
<Setter Property="Template">
<ControlTemplate>
<Border
Margin="{TemplateBinding Margin}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<ScrollViewer
Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<DockPanel>
<ItemsPresenter
Name="PART_ItemsPresenter"
DockPanel.Dock="Right"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}" >
</ItemsPresenter>
<ContentPresenter
Name="PART_Content"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</Styles>

45
src/Avalonia.Themes.Default/TabItem.xaml

@ -0,0 +1,45 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="TabItem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="2 1"/>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter
Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Content="{TemplateBinding Header}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Margin}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabItem:disabled">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>
<Style Selector="TabItem:pointerover /template/ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
</Style>
<Style Selector="TabItem:selected /template/ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
</Style>
<Style Selector="TabItem:selected:focus /template/ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
</Style>
<Style Selector="ListBoxItem:selected:pointerover /template/ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
</Style>
<Style Selector="ListBoxItem:selected:focus:pointerover /template/ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
</Style>
</Styles>

145
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests
TabItem selected; TabItem selected;
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = new[] Items = new[]
{ {
(selected = new TabItem (selected = new TabItem
@ -61,7 +61,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = items, Items = items,
}; };
@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = collection, Items = collection,
}; };
@ -147,7 +147,7 @@ namespace Avalonia.Controls.UnitTests
}, },
Child = new TabControl Child = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = collection, Items = collection,
} }
}; };
@ -172,7 +172,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
DataContext = "Base", DataContext = "Base",
DataTemplates = DataTemplates =
{ {
@ -182,41 +182,39 @@ namespace Avalonia.Controls.UnitTests
}; };
ApplyTemplate(target); ApplyTemplate(target);
var carousel = (Carousel)target.Pages;
var container = (ContentPresenter)carousel.Presenter.Panel.Children.Single(); target.ContentPart.UpdateChild();
container.UpdateChild(); var dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
var dataContext = ((TextBlock)container.Child).DataContext;
Assert.Equal(items[0], dataContext); Assert.Equal(items[0], dataContext);
target.SelectedIndex = 1; target.SelectedIndex = 1;
container = (ContentPresenter)carousel.Presenter.Panel.Children.Single(); target.ContentPart.UpdateChild();
container.UpdateChild(); dataContext = ((Button)target.ContentPart.Child).DataContext;
dataContext = ((Button)container.Child).DataContext;
Assert.Equal(items[1], dataContext); Assert.Equal(items[1], dataContext);
target.SelectedIndex = 2; target.SelectedIndex = 2;
dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext; target.ContentPart.UpdateChild();
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Base", dataContext); Assert.Equal("Base", dataContext);
target.SelectedIndex = 3; target.SelectedIndex = 3;
container = (ContentPresenter)carousel.Presenter.Panel.Children[0]; target.ContentPart.UpdateChild();
container.UpdateChild(); dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
dataContext = ((TextBlock)container.Child).DataContext;
Assert.Equal("Qux", dataContext); Assert.Equal("Qux", dataContext);
target.SelectedIndex = 4; target.SelectedIndex = 4;
dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext; target.ContentPart.UpdateChild();
dataContext = target.ContentPart.DataContext;
Assert.Equal("Base", dataContext); Assert.Equal("Base", dataContext);
} }
/// <summary> /// <summary>
/// Non-headered control items should result in TabStripItems with empty content. /// Non-headered control items should result in TabItems with empty header.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// If a TabStrip is created with non IHeadered controls as its items, don't try to /// If a TabControl 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 /// display the control in the header: if the control is part of the header then
/// then *that* will also try to display the control, resulting in dual-parentage /// *that* control would also end up in the content region, resulting in dual-parentage
/// breakage. /// breakage.
/// </remarks> /// </remarks>
[Fact] [Fact]
@ -230,18 +228,20 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = items, Items = items,
}; };
ApplyTemplate(target); ApplyTemplate(target);
var result = target.TabStrip.GetLogicalChildren() var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren();
.OfType<TabStripItem>()
.Select(x => x.Content) var result = logicalChildren
.OfType<TabItem>()
.Select(x => x.Header)
.ToList(); .ToList();
Assert.Equal(new object[] { string.Empty, string.Empty }, result); Assert.Equal(new object[] { null, null }, result);
} }
[Fact] [Fact]
@ -249,7 +249,7 @@ namespace Avalonia.Controls.UnitTests
{ {
TabControl target = new TabControl TabControl target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = new[] Items = new[]
{ {
new TabItem { Header = "Foo" }, new TabItem { Header = "Foo" },
@ -262,70 +262,61 @@ namespace Avalonia.Controls.UnitTests
target.SelectedIndex = 2; target.SelectedIndex = 2;
var carousel = (Carousel)target.Pages; var page = (TabItem)target.SelectedItem;
var page = (TabItem)carousel.SelectedItem;
Assert.Null(page.Content); Assert.Null(page.Content);
} }
private Control CreateTabControlTemplate(TabControl parent) private IControlTemplate TabControlTemplate()
{ {
return new StackPanel return new FuncControlTemplate<TabControl>(parent =>
{
Children =
{
new TabStrip
{
Name = "PART_TabStrip",
Template = new FuncControlTemplate<TabStrip>(CreateTabStripTemplate),
MemberSelector = TabControl.HeaderSelector,
[!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
[!!TabStrip.SelectedIndexProperty] = parent[!!TabControl.SelectedIndexProperty]
},
new Carousel
{
Name = "PART_Content",
Template = new FuncControlTemplate<Carousel>(CreateCarouselTemplate),
MemberSelector = TabControl.ContentSelector,
[!Carousel.ItemsProperty] = parent[!TabControl.ItemsProperty],
[!Carousel.SelectedItemProperty] = parent[!TabControl.SelectedItemProperty],
}
}
};
}
private Control CreateTabStripTemplate(TabStrip parent) new StackPanel
{ {
return new ItemsPresenter Children = {
{ new ItemsPresenter
Name = "PART_ItemsPresenter", {
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], Name = "PART_ItemsPresenter",
[!CarouselPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty], [!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 return new FuncControlTemplate<TabItem>(parent =>
{ new ContentPresenter
Name = "PART_ItemsPresenter", {
[!CarouselPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty], Name = "PART_ContentPresenter",
[!CarouselPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty], [!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty],
[!CarouselPresenter.MemberSelectorProperty] = control[!ItemsControl.MemberSelectorProperty], [!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty]
[!CarouselPresenter.SelectedIndexProperty] = control[!SelectingItemsControl.SelectedIndexProperty], });
[~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty],
};
} }
private void ApplyTemplate(TabControl target) private void ApplyTemplate(TabControl target)
{ {
target.ApplyTemplate(); target.ApplyTemplate();
var carousel = (Carousel)target.Pages;
carousel.ApplyTemplate(); target.Presenter.ApplyTemplate();
carousel.Presenter.ApplyTemplate();
var tabStrip = (TabStrip)target.TabStrip; foreach (var tabItem in target.GetLogicalChildren().OfType<TabItem>())
tabStrip.ApplyTemplate(); {
tabStrip.Presenter.ApplyTemplate(); tabItem.Template = TabItemTemplate();
tabItem.ApplyTemplate();
((ContentPresenter)tabItem.Presenter).UpdateChild();
}
target.ContentPart.ApplyTemplate();
} }
private class Item private class Item

Loading…
Cancel
Save