Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docfx/articles/dock-controls-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ For behavior details and keyboard validation guidance, see [Accessibility and UI
| `Orientation` | `Orientation` | Tab strip orientation. |
| `MouseWheelScrollOrientation` | `Orientation` | Mouse-wheel scroll axis for tab overflow (`Horizontal` by default). |
| `CreateButtonTheme` | `ControlTheme?` | Theme for the create document button. |
| `IconTemplate` | `object?` | Tab icon template used by `DocumentTabStripItem`. |
| `HeaderTemplate` | `IDataTemplate?` | Tab header template used by `DocumentTabStripItem`. |
| `ModifiedTemplate` | `IDataTemplate?` | Modified indicator template used by `DocumentTabStripItem`. |
| `CloseTemplate` | `IDataTemplate?` | Close template used by `DocumentTabStripItem`. |

### DocumentTabStripItem

Expand All @@ -145,6 +149,9 @@ For behavior details and keyboard validation guidance, see [Accessibility and UI
| --- | --- | --- |
| `CanCreateItem` | `bool` | `true` when the new-tool button is available. |
| `MouseWheelScrollOrientation` | `Orientation` | Mouse-wheel scroll axis for tab overflow (`Horizontal` by default). |
| `IconTemplate` | `object?` | Tab icon template used by `ToolTabStripItem`. |
| `HeaderTemplate` | `IDataTemplate?` | Tab header template used by `ToolTabStripItem`. |
| `ModifiedTemplate` | `IDataTemplate?` | Modified indicator template used by `ToolTabStripItem`. |

### ToolTabStripItem

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
ItemsSource="{Binding VisibleDockables}"
SelectedItem="{Binding ActiveDockable, Mode=TwoWay}"
CanCreateItem="{Binding CanCreateDocument}"
IconTemplate="{TemplateBinding IconTemplate}"
HeaderTemplate="{TemplateBinding HeaderTemplate}"
ModifiedTemplate="{TemplateBinding ModifiedTemplate}"
CloseTemplate="{TemplateBinding CloseTemplate}"
IsActive="{TemplateBinding IsActive}"
Orientation="{Binding TabsLayout, Converter={x:Static DocumentTabOrientationConverter.Instance}}"
DockPanel.Dock="{Binding TabsLayout, Converter={x:Static DocumentTabDockConverter.Instance}}"
Expand Down Expand Up @@ -88,6 +92,10 @@
ItemsSource="{Binding VisibleDockables}"
SelectedItem="{Binding ActiveDockable, Mode=TwoWay}"
CanCreateItem="{Binding CanCreateDocument}"
IconTemplate="{TemplateBinding IconTemplate}"
HeaderTemplate="{TemplateBinding HeaderTemplate}"
ModifiedTemplate="{TemplateBinding ModifiedTemplate}"
CloseTemplate="{TemplateBinding CloseTemplate}"
IsActive="{TemplateBinding IsActive}"
Orientation="{Binding TabsLayout, Converter={x:Static DocumentTabOrientationConverter.Instance}}"
DockPanel.Dock="{Binding TabsLayout, Converter={x:Static DocumentTabDockConverter.Instance}}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,18 +360,18 @@
<Grid x:Name="PART_IconCloseHost"
ClipToBounds="False">
<ContentPresenter x:Name="PART_IconPresenter"
ContentTemplate="{Binding $parent[DocumentControl].IconTemplate}"
ContentTemplate="{Binding $parent[DocumentTabStrip].IconTemplate}"
Content="{Binding}" />
<ContentPresenter x:Name="PART_CompactClosePresenter"
IsVisible="False"
ContentTemplate="{Binding $parent[DocumentControl].CloseTemplate}"
ContentTemplate="{Binding $parent[DocumentTabStrip].CloseTemplate}"
Content="{Binding}" />
</Grid>
<ContentPresenter x:Name="PART_HeaderPresenter"
ContentTemplate="{Binding $parent[DocumentControl].HeaderTemplate}"
ContentTemplate="{Binding $parent[DocumentTabStrip].HeaderTemplate}"
Content="{Binding}" />
<ContentPresenter x:Name="PART_ModifiedPresenter"
ContentTemplate="{Binding $parent[DocumentControl].ModifiedTemplate}"
ContentTemplate="{Binding $parent[DocumentTabStrip].ModifiedTemplate}"
IsVisible="{Binding IsModified}"
Content="{Binding}" />
</StackPanel>
Expand All @@ -380,7 +380,7 @@
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="{DynamicResource DockTabContentMargin}"
ContentTemplate="{Binding $parent[DocumentControl].CloseTemplate}"
ContentTemplate="{Binding $parent[DocumentTabStrip].CloseTemplate}"
Content="{Binding}" />
</Grid>
</DockPanel>
Expand Down
3 changes: 3 additions & 0 deletions src/Dock.Avalonia.Themes.Fluent/Controls/ToolControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
<ToolTabStrip x:Name="PART_TabStrip"
ItemsSource="{Binding VisibleDockables}"
SelectedItem="{Binding ActiveDockable, Mode=TwoWay}"
IconTemplate="{TemplateBinding IconTemplate}"
HeaderTemplate="{TemplateBinding HeaderTemplate}"
ModifiedTemplate="{TemplateBinding ModifiedTemplate}"
DockPanel.Dock="Bottom"
DockProperties.IsDropArea="True"
DockProperties.DockAdornerHost="{Binding #PART_DockPanel}" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,15 @@
Orientation="Horizontal"
Spacing="{DynamicResource DockTabContentSpacing}">
<StackPanel Margin="{DynamicResource DockTabContentMargin}" Orientation="Horizontal">
<ContentPresenter ContentTemplate="{Binding $parent[ToolControl].IconTemplate}"
<ContentPresenter x:Name="PART_IconPresenter"
ContentTemplate="{Binding $parent[ToolTabStrip].IconTemplate}"
Content="{Binding}" />
<ContentPresenter ContentTemplate="{Binding $parent[ToolControl].HeaderTemplate}"
<ContentPresenter x:Name="PART_HeaderPresenter"
ContentTemplate="{Binding $parent[ToolTabStrip].HeaderTemplate}"
Content="{Binding}" />
</StackPanel>
<ContentPresenter ContentTemplate="{Binding $parent[ToolControl].ModifiedTemplate}"
<ContentPresenter x:Name="PART_ModifiedPresenter"
ContentTemplate="{Binding $parent[ToolTabStrip].ModifiedTemplate}"
IsVisible="{Binding IsModified}"
Content="{Binding}" />
</StackPanel>
Expand Down
60 changes: 60 additions & 0 deletions src/Dock.Avalonia/Controls/DocumentTabStrip.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,30 @@ public class DocumentTabStrip : TabStrip
public static readonly StyledProperty<ControlTheme?> CreateButtonThemeProperty =
AvaloniaProperty.Register<DocumentTabStrip, ControlTheme?>(nameof(CreateButtonTheme));

/// <summary>
/// Defines the <see cref="IconTemplate"/> property.
/// </summary>
public static readonly StyledProperty<object?> IconTemplateProperty =
AvaloniaProperty.Register<DocumentTabStrip, object?>(nameof(IconTemplate));

/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> HeaderTemplateProperty =
AvaloniaProperty.Register<DocumentTabStrip, IDataTemplate?>(nameof(HeaderTemplate));

/// <summary>
/// Defines the <see cref="ModifiedTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> ModifiedTemplateProperty =
AvaloniaProperty.Register<DocumentTabStrip, IDataTemplate?>(nameof(ModifiedTemplate));

/// <summary>
/// Defines the <see cref="CloseTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> CloseTemplateProperty =
AvaloniaProperty.Register<DocumentTabStrip, IDataTemplate?>(nameof(CloseTemplate));

/// <summary>
/// Defines the <see cref="CreateButtonTemplate"/> property.
/// </summary>
Expand Down Expand Up @@ -142,6 +166,42 @@ public ControlTheme? CreateButtonTheme
set => SetValue(CreateButtonThemeProperty, value);
}

/// <summary>
/// Gets or sets tab icon template.
/// </summary>
public object? IconTemplate
{
get => GetValue(IconTemplateProperty);
set => SetValue(IconTemplateProperty, value);
}

/// <summary>
/// Gets or sets tab header template.
/// </summary>
public IDataTemplate? HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}

/// <summary>
/// Gets or sets tab modified template.
/// </summary>
public IDataTemplate? ModifiedTemplate
{
get => GetValue(ModifiedTemplateProperty);
set => SetValue(ModifiedTemplateProperty, value);
}

/// <summary>
/// Gets or sets tab close template.
/// </summary>
public IDataTemplate? CloseTemplate
{
get => GetValue(CloseTemplateProperty);
set => SetValue(CloseTemplateProperty, value);
}

/// <summary>
/// Gets or sets the create button content template.
/// </summary>
Expand Down
46 changes: 46 additions & 0 deletions src/Dock.Avalonia/Controls/ToolTabStrip.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Reactive;
Expand Down Expand Up @@ -44,6 +45,24 @@ public class ToolTabStrip : TabStrip
nameof(MouseWheelScrollOrientation),
defaultValue: Orientation.Horizontal);

/// <summary>
/// Defines the <see cref="IconTemplate"/> property.
/// </summary>
public static readonly StyledProperty<object?> IconTemplateProperty =
AvaloniaProperty.Register<ToolTabStrip, object?>(nameof(IconTemplate));

/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> HeaderTemplateProperty =
AvaloniaProperty.Register<ToolTabStrip, IDataTemplate?>(nameof(HeaderTemplate));

/// <summary>
/// Defines the <see cref="ModifiedTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> ModifiedTemplateProperty =
AvaloniaProperty.Register<ToolTabStrip, IDataTemplate?>(nameof(ModifiedTemplate));

/// <summary>
/// Gets or sets if tab strop dock can create new items.
/// </summary>
Expand All @@ -62,6 +81,33 @@ public Orientation MouseWheelScrollOrientation
set => SetValue(MouseWheelScrollOrientationProperty, value);
}

/// <summary>
/// Gets or sets tab icon template.
/// </summary>
public object? IconTemplate
{
get => GetValue(IconTemplateProperty);
set => SetValue(IconTemplateProperty, value);
}

/// <summary>
/// Gets or sets tab header template.
/// </summary>
public IDataTemplate? HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}

/// <summary>
/// Gets or sets tab modified template.
/// </summary>
public IDataTemplate? ModifiedTemplate
{
get => GetValue(ModifiedTemplateProperty);
set => SetValue(ModifiedTemplateProperty, value);
}

/// <inheritdoc/>
protected override Type StyleKeyOverride => typeof(ToolTabStrip);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Headless.XUnit;
using Avalonia.VisualTree;
using Dock.Avalonia.Controls;
using Dock.Model.Avalonia;
using Dock.Model.Avalonia.Controls;
using Dock.Model.Core;
using Xunit;

namespace Dock.Avalonia.HeadlessTests;

public class DocumentTabStripTemplateBindingTests
{
[AvaloniaFact]
public void Standalone_DocumentTabStrip_Uses_All_Item_Templates()
{
var templates = CreateTemplates();
var document = new Document { Id = "doc-1", Title = "Doc 1", IsModified = true };
var tabStrip = new DocumentTabStrip
{
Width = 320,
Height = 48,
IconTemplate = templates.IconTemplate,
HeaderTemplate = templates.HeaderTemplate,
ModifiedTemplate = templates.ModifiedTemplate,
CloseTemplate = templates.CloseTemplate,
ItemsSource = new AvaloniaList<IDockable> { document }
};

var window = ShowInWindow(tabStrip);
try
{
var tabItem = GetTabItem(tabStrip, 0);

Assert.Same(templates.IconTemplate, GetPresenter(tabItem, "PART_IconPresenter").ContentTemplate);
Assert.Same(templates.HeaderTemplate, GetPresenter(tabItem, "PART_HeaderPresenter").ContentTemplate);
Assert.Same(templates.ModifiedTemplate, GetPresenter(tabItem, "PART_ModifiedPresenter").ContentTemplate);
Assert.Same(templates.CloseTemplate, GetPresenter(tabItem, "PART_ClosePresenter").ContentTemplate);

var renderedTexts = tabItem.GetVisualDescendants()
.OfType<TextBlock>()
.Select(textBlock => textBlock.Text ?? string.Empty)
.ToArray();

Assert.Contains("icon:Doc 1", renderedTexts);
Assert.Contains("header:Doc 1", renderedTexts);
Assert.Contains("modified:Doc 1", renderedTexts);
Assert.Contains("close:Doc 1", renderedTexts);
}
finally
{
window.Close();
}
}

[AvaloniaFact]
public void DocumentControl_Forwards_Templates_To_DocumentTabStrip()
{
var templates = CreateTemplates();
var factory = new Factory();
var dock = new DocumentDock
{
Factory = factory,
LayoutMode = DocumentLayoutMode.Tabbed,
VisibleDockables = factory.CreateList<IDockable>()
};

var document = new Document { Id = "doc-1", Title = "Doc 1", IsModified = true };
dock.VisibleDockables!.Add(document);
dock.ActiveDockable = document;

var control = new DocumentControl
{
DataContext = dock,
IconTemplate = templates.IconTemplate,
HeaderTemplate = templates.HeaderTemplate,
ModifiedTemplate = templates.ModifiedTemplate,
CloseTemplate = templates.CloseTemplate
};

var window = ShowInWindow(control);
try
{
var tabStrip = control.GetVisualDescendants().OfType<DocumentTabStrip>().FirstOrDefault();
Assert.NotNull(tabStrip);

Assert.Same(templates.IconTemplate, tabStrip!.IconTemplate);
Assert.Same(templates.HeaderTemplate, tabStrip.HeaderTemplate);
Assert.Same(templates.ModifiedTemplate, tabStrip.ModifiedTemplate);
Assert.Same(templates.CloseTemplate, tabStrip.CloseTemplate);

var tabItem = GetTabItem(tabStrip, 0);
Assert.Same(templates.CloseTemplate, GetPresenter(tabItem, "PART_ClosePresenter").ContentTemplate);
}
finally
{
window.Close();
}
}

private static (IDataTemplate IconTemplate, IDataTemplate HeaderTemplate, IDataTemplate ModifiedTemplate, IDataTemplate CloseTemplate) CreateTemplates()
{
var iconTemplate = new FuncDataTemplate<IDockable>((dockable, _) => new TextBlock { Text = $"icon:{dockable.Title}" }, true);
var headerTemplate = new FuncDataTemplate<IDockable>((dockable, _) => new TextBlock { Text = $"header:{dockable.Title}" }, true);
var modifiedTemplate = new FuncDataTemplate<IDockable>((dockable, _) => new TextBlock { Text = $"modified:{dockable.Title}" }, true);
var closeTemplate = new FuncDataTemplate<IDockable>((dockable, _) => new TextBlock { Text = $"close:{dockable.Title}" }, true);
return (iconTemplate, headerTemplate, modifiedTemplate, closeTemplate);
}

private static Window ShowInWindow(Control control)
{
var window = new Window
{
Width = 600,
Height = 400,
Content = control
};

window.Show();
control.ApplyTemplate();
window.UpdateLayout();
control.UpdateLayout();
return window;
}

private static DocumentTabStripItem GetTabItem(DocumentTabStrip tabStrip, int index)
{
var tabItem = tabStrip.ContainerFromIndex(index) as DocumentTabStripItem;
Assert.NotNull(tabItem);
tabItem!.ApplyTemplate();
tabItem.UpdateLayout();
return tabItem;
}

private static ContentPresenter GetPresenter(DocumentTabStripItem tabItem, string presenterName)
{
var presenter = tabItem.GetVisualDescendants()
.OfType<ContentPresenter>()
.FirstOrDefault(candidate => candidate.Name == presenterName);
Assert.NotNull(presenter);
return presenter!;
}
}
Loading
Loading