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
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Choose the implementation that matches your MVVM framework:
- [Deep dive](dock-deep-dive.md) – Internals of `DockControl`.
- [DockManager guide](dock-manager-guide.md) – When and how to customize `DockManager`.
- [Styling and theming](dock-styling.md) – Customize the appearance of Dock controls.
- [DataTemplates and custom dock types](dock-datatemplates.md) – Create custom dock types with their own visual representation.
- [Custom themes](dock-custom-theme.md) – Build and apply your own theme.
- [Context menus](dock-context-menus.md) – Localize or replace built in menus.
- [Control recycling](dock-control-recycling.md) – Reuse visuals when dockables return.
Expand Down
164 changes: 164 additions & 0 deletions docs/dock-datatemplates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# DataTemplates and Custom Dock Types

This guide explains how DockControl uses DataTemplates to render different dock elements and how to create custom dock types with their own visual representation.

## Overview

The Dock library uses Avalonia's DataTemplate system to render different types of dock elements. Each dock type (documents, tools, containers, splitters) has a corresponding DataTemplate that defines how it should be visually represented.

### Key Components

- **DockControl**: The main control that hosts dock layouts and manages DataTemplates
- **DockDataTemplateHelper**: Centralized creation of built-in DataTemplates
- **RequiresDataTemplateAttribute**: Marks interfaces that need DataTemplates
- **AutoCreateDataTemplates**: Property to control automatic DataTemplate creation

## How DataTemplates Work

DockControl automatically creates DataTemplates for all dock types when its template is applied. The process works as follows:

1. **Automatic Creation**: When `AutoCreateDataTemplates` is `true` (default), DockControl automatically adds DataTemplates for all built-in dock types
2. **Template Matching**: Avalonia matches dock objects to their corresponding DataTemplate based on type
3. **Visual Rendering**: The matched DataTemplate creates the appropriate control to render the dock element

### Controlling DataTemplate Creation

You can control DataTemplate behavior using the `AutoCreateDataTemplates` property:

```csharp
// Disable automatic creation to use custom templates
dockControl.AutoCreateDataTemplates = false;

// Or in XAML
<DockControl AutoCreateDataTemplates="False" />
```

## RequiresDataTemplateAttribute

The `RequiresDataTemplateAttribute` marks dock interfaces that need DataTemplates. This ensures all dock types have proper visual representation.

### Usage

When creating custom dock types, mark the interface with this attribute:

```csharp
[RequiresDataTemplate]
public interface IMyCustomDock : IDock
{
// Interface members
}
```

This attribute serves two purposes:
- **Documentation**: Clearly indicates which interfaces need visual representation
- **Testing**: Unit tests automatically verify that all marked interfaces have DataTemplates

## Built-in DataTemplates

The library provides DataTemplates for all built-in dock types:

**Content Types:**
- `IDocumentContent` → `DocumentContentControl`
- `IToolContent` → `ToolContentControl`

**Container Types:**
- `IDocumentDock` → `DocumentDockControl`
- `IToolDock` → `ToolDockControl`
- `IProportionalDock` → `ProportionalDockControl`
- `IStackDock` → `StackDockControl`
- `IGridDock` → `GridDockControl`
- `IWrapDock` → `WrapDockControl`
- `IUniformGridDock` → `UniformGridDockControl`
- `IDockDock` → `DockDockControl`
- `IRootDock` → `RootDockControl`

**Splitter Types:**
- `IProportionalDockSplitter` → `ProportionalStackPanelSplitter`
- `IGridDockSplitter` → `GridSplitter`

All DataTemplates are created automatically by `DockDataTemplateHelper` using Avalonia's binding syntax.

## Creating Custom Dock Types

To create a custom dock type with its own visual representation:

### 1. Define the Interface

Create your custom dock interface and mark it with the attribute:

```csharp
[RequiresDataTemplate]
public interface ICustomTabDock : IDock
{
string TabStyle { get; set; }
bool ShowTabIcons { get; set; }
}
```

### 2. Implement the Interface

Create a concrete implementation using your preferred model (Avalonia, MVVM, ReactiveUI, etc.):

```csharp
public class CustomTabDock : Dock, ICustomTabDock
{
public string TabStyle { get; set; } = "Default";
public bool ShowTabIcons { get; set; } = true;
}
```

### 3. Create the Control

Design the visual control for your dock type:

```csharp
public class CustomTabDockControl : TemplatedControl
{
// Implement your custom control logic
}
```

### 4. Add the DataTemplate

**Option A: In Code (Recommended)**

```csharp
// Add your custom template alongside the default ones
var customTemplate = new FuncDataTemplate<ICustomTabDock>(
(data, scope) => new CustomTabDockControl { DataContext = data });
dockControl.DataTemplates.Add(customTemplate);

// Default templates are automatically added if AutoCreateDataTemplates is true (default)
// To disable default templates entirely, set AutoCreateDataTemplates = false
```

**Option B: In XAML**

```xml
<DockControl>
<DockControl.DataTemplates>
<!-- Your custom template is added alongside the default ones -->
<DataTemplate DataType="{x:Type model:ICustomTabDock}">
<controls:CustomTabDockControl />
</DataTemplate>
</DockControl.DataTemplates>
</DockControl>
```

## Best Practices

When working with custom dock types and DataTemplates:

1. **Always mark custom dock interfaces** with `[RequiresDataTemplate]`
2. **Use descriptive naming** following the pattern: `IMyDock`, `MyDock`, `MyDockControl`
3. **Add custom templates directly** - they work alongside the default templates automatically
4. **Test your custom types** to ensure DataTemplate coverage
5. **Use the bang operator** (`!`) for property bindings when creating templates programmatically
6. **Consider inheritance** - derive from existing dock types when possible
7. **Only disable `AutoCreateDataTemplates`** when you want to provide all templates manually

## See Also

- [Creating Custom Models](dock-custom-model.md) - Learn about implementing custom dock models
- [Styling Guide](dock-styling.md) - Customize the appearance of dock controls
- [Sample Applications](../samples/) - Examples of custom dock implementations
45 changes: 1 addition & 44 deletions src/Dock.Avalonia.Themes.Fluent/Controls/DockControl.axaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dmc="using:Dock.Model.Controls"
xmlns:core="using:Dock.Model.Core"
x:DataType="core:IDock"
x:CompileBindings="True">
Expand All @@ -20,49 +19,7 @@
<Setter Property="Template">
<ControlTemplate>
<ContentControl x:Name="PART_ContentControl"
Content="{TemplateBinding Layout}">
<ContentControl.DataTemplates>
<DataTemplate DataType="dmc:IDocumentContent">
<DocumentContentControl />
</DataTemplate>
<DataTemplate DataType="dmc:IToolContent">
<ToolContentControl />
</DataTemplate>
<DataTemplate DataType="dmc:IProportionalDockSplitter">
<ProportionalStackPanelSplitter IsResizingEnabled="{Binding CanResize}" PreviewResize="{Binding ResizePreview}" />
</DataTemplate>
<DataTemplate DataType="dmc:IGridDockSplitter">
<GridSplitter ResizeDirection="{Binding ResizeDirection}" />
</DataTemplate>
<DataTemplate DataType="dmc:IDocumentDock">
<DocumentDockControl />
</DataTemplate>
<DataTemplate DataType="dmc:IToolDock">
<ToolDockControl />
</DataTemplate>
<DataTemplate DataType="dmc:IProportionalDock">
<ProportionalDockControl />
</DataTemplate>
<DataTemplate DataType="dmc:IStackDock">
<StackDockControl />
</DataTemplate>
<DataTemplate DataType="dmc:IGridDock">
<GridDockControl />
</DataTemplate>
<DataTemplate DataType="dmc:IWrapDock">
<WrapDockControl />
</DataTemplate>
<DataTemplate DataType="dmc:IUniformGridDock">
<UniformGridDockControl />
</DataTemplate>
<DataTemplate DataType="dmc:IDockDock">
<DockDockControl />
</DataTemplate>
<DataTemplate DataType="dmc:IRootDock">
<RootDockControl />
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
Content="{TemplateBinding Layout}" />
</ControlTemplate>
</Setter>

Expand Down
58 changes: 57 additions & 1 deletion src/Dock.Avalonia/Controls/DockControl.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
Expand All @@ -14,18 +16,21 @@
using Dock.Avalonia.Diagnostics;
using Dock.Avalonia.Internal;
using Dock.Model;
using Dock.Model.Controls;
using Dock.Model.Core;

namespace Dock.Avalonia.Controls;

/// <summary>
/// Interaction logic for <see cref="DockControl"/> xaml.
/// </summary>
[TemplatePart("PART_ContentControl", typeof(ContentControl))]
public class DockControl : TemplatedControl, IDockControl
{
private readonly DockManager _dockManager;
private readonly DockControlState _dockControlState;
private bool _isInitialized;
private ContentControl? _contentControl;

/// <summary>
/// Defines the <see cref="Layout"/> property.
Expand Down Expand Up @@ -63,6 +68,12 @@ public class DockControl : TemplatedControl, IDockControl
public static readonly StyledProperty<bool> IsDraggingDockProperty =
AvaloniaProperty.Register<DockControl, bool>(nameof(IsDraggingDock));

/// <summary>
/// Defines the <see cref="AutoCreateDataTemplates"/> property.
/// </summary>
public static readonly StyledProperty<bool> AutoCreateDataTemplatesProperty =
AvaloniaProperty.Register<DockControl, bool>(nameof(AutoCreateDataTemplates), true);

/// <inheritdoc/>
public IDockManager DockManager => _dockManager;

Expand Down Expand Up @@ -114,6 +125,17 @@ public bool IsDraggingDock
set => SetValue(IsDraggingDockProperty, value);
}

/// <summary>
/// Gets or sets whether to automatically create default DataTemplates in code-behind.
/// When true (default), the control will add default DataTemplates for all dock types.
/// When false, no DataTemplates are added, allowing complete user control via XAML.
/// </summary>
public bool AutoCreateDataTemplates
{
get => GetValue(AutoCreateDataTemplatesProperty);
set => SetValue(AutoCreateDataTemplatesProperty, value);
}

private IDragOffsetCalculator _dragOffsetCalculator = new DefaultDragOffsetCalculator();

/// <summary>
Expand Down Expand Up @@ -148,6 +170,39 @@ public DockControl()
AddHandler(PointerWheelChangedEvent, WheelChangedHandler, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
}

/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

_contentControl = e.NameScope.Find<ContentControl>("PART_ContentControl");

if (_contentControl is not null)
{
InitializeDefaultDataTemplates();
}
}

private void InitializeDefaultDataTemplates()
{
if (_contentControl?.DataTemplates is null)
{
return;
}

// Check if auto-creation of DataTemplates is enabled
if (!AutoCreateDataTemplates)
{
return;
}

// Create and add default DataTemplates using helper class
foreach (var template in DockDataTemplateHelper.CreateDefaultDataTemplates())
{
_contentControl.DataTemplates.Add(template);
}
}

/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
Expand Down Expand Up @@ -336,9 +391,10 @@ private void PressedHandler(object? sender, PointerPressedEventArgs e)
if (e.KeyModifiers.HasFlag(KeyModifiers.Control) && e.KeyModifiers.HasFlag(KeyModifiers.Shift))
{
var pos = e.GetPosition(this);
if (this.InputHitTest(pos) is Control control)
if (this.InputHitTest(pos) is Control initialControl)
{
IDockable? dockable = null;
Control? control = initialControl;
while (control is { } && dockable is null)
{
dockable = control.DataContext as IDockable;
Expand Down
Loading
Loading