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
62 changes: 57 additions & 5 deletions docfx/articles/dock-itemssource.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ The `DocumentDock.ItemsSource` and `ToolDock.ItemsSource` properties enable auto

`ItemsSource` is implemented by `Dock.Model.Avalonia.Controls.DocumentDock` and `Dock.Model.Avalonia.Controls.ToolDock` in the Avalonia model layer:

- `DocumentDock.ItemsSource` requires `DocumentTemplate`.
- `ToolDock.ItemsSource` requires `ToolTemplate`.
- By default, `DocumentDock.ItemsSource` requires `DocumentTemplate`.
- By default, `ToolDock.ItemsSource` requires `ToolTemplate`.
- `DocumentDock.ItemContainerGenerator` and `ToolDock.ItemContainerGenerator` let you override container creation and preparation.

If no template is supplied, no generated dockables are created.
With the default generator, if no template is supplied, no generated dockables are created. A custom `ItemContainerGenerator` can override this behavior.

## Behavior details

- `DocumentDock` creates a `Document` for each item and stores the item in `Document.Context`.
- `ToolDock` creates a `Tool` for each item and stores the item in `Tool.Context`.
- By default, `DocumentDock` creates a `Document` for each item and stores the item in `Document.Context`.
- By default, `ToolDock` creates a `Tool` for each item and stores the item in `Tool.Context`.
- The tab title is derived from `Title`, `Name`, or `DisplayName` properties on the item (in that order), falling back to `ToString()`.
- `CanClose` is copied from the item if present; otherwise it defaults to `true`.
- When a generated document or tool is closed, the factory attempts to remove the source item from `ItemsSource` if it implements `IList`.
- Source-generated document/tool closes are treated as remove operations, even when `Factory.HideDocumentsOnClose` or `Factory.HideToolsOnClose` is enabled.
- You can replace the default generation pipeline with `IDockItemContainerGenerator` for custom container types, metadata mapping, and cleanup.

## Key Benefits

Expand Down Expand Up @@ -317,6 +319,56 @@ public enum DocumentType
</DocumentDock>
```

### Custom Container Generator

Use `IDockItemContainerGenerator` when you need custom container types or custom preparation/cleanup logic for source-generated dockables.

```csharp
public sealed class MyGenerator : DockItemContainerGenerator
{
public override IDockable? CreateDocumentContainer(IItemsSourceDock dock, object item, int index)
{
return new MyDocumentContainer { Id = $"Doc-{index}" };
}

public override void PrepareDocumentContainer(IItemsSourceDock dock, IDockable container, object item, int index)
{
base.PrepareDocumentContainer(dock, container, item, index);
container.Title = $"Document {container.Title}";
}

public override IDockable? CreateToolContainer(IToolItemsSourceDock dock, object item, int index)
{
return new MyToolContainer { Id = $"Tool-{index}" };
}
}
```

Assign the generator per dock:

```xaml
<Window.Resources>
<local:MyGenerator x:Key="MyGenerator" />
</Window.Resources>

<ToolDock ItemsSource="{Binding Tools}"
ToolTemplate="{StaticResource ToolTemplate}"
ItemContainerGenerator="{StaticResource MyGenerator}" />

<DocumentDock ItemsSource="{Binding Documents}"
DocumentTemplate="{StaticResource DocumentTemplate}"
ItemContainerGenerator="{StaticResource MyGenerator}" />
```

`Dock.Model.Avalonia.Controls.DockItemContainerGenerator` provides the default behavior and can be subclassed, or you can implement `IDockItemContainerGenerator` from scratch.

Container compatibility contract:

- `CreateDocumentContainer` should return an `IDocument` implementation (for example `Document` or a derived type).
- `CreateToolContainer` should return an `ITool` implementation (for example `Tool` or a derived type).

Incompatible container types are skipped by the pipeline and immediately cleared.

### Custom Commands Integration

```csharp
Expand Down
11 changes: 11 additions & 0 deletions docfx/articles/dock-model-controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ concept with a `ToolTemplate` and `CreateToolFromTemplate`. This mirrors
`IDocumentDockContent` for tool panes. In Avalonia, `ToolDock.ItemsSource`
uses this template to create generated tool content from source collections.

## IItemsSourceDock and IToolItemsSourceDock

These interfaces define source-backed dock generation contracts:

- `ItemsSource` exposes the source collection used to generate dockables.
- `ItemContainerGenerator` provides custom container creation/preparation/cleanup through `IDockItemContainerGenerator`.
- `IsDocumentFromItemsSource` / `IsToolFromItemsSource` detect source-generated dockables.
- `RemoveItemFromSource` synchronizes close operations back to mutable source collections.

The Avalonia model's `DocumentDock` and `ToolDock` implementations use `DockItemContainerGenerator` by default, while still allowing per-dock generator overrides.

## IToolTemplate

`IToolTemplate` represents template content used to build tool views. In
Expand Down
5 changes: 4 additions & 1 deletion docfx/articles/dock-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,13 @@ The factory provides helper methods `SetDocumentDockTabsLayoutLeft`, `SetDocumen

- `DocumentDock.ItemsSource` + `DocumentTemplate` generate `Document` dockables.
- `ToolDock.ItemsSource` + `ToolTemplate` generate `Tool` dockables.
- `DocumentDock.ItemContainerGenerator` and `ToolDock.ItemContainerGenerator` accept `IDockItemContainerGenerator` for custom create/prepare/clear pipelines.
- `IsDocumentFromItemsSource(IDockable)` / `IsToolFromItemsSource(IDockable)` report whether the dockable was generated from the bound source.
- `RemoveItemFromSource(object)` removes source items from supported list collections.

When `ItemsSource` is set (and the required template is provided), Dock automatically creates dockables for each source item. The generated `Title` is derived from `Title`, `Name`, or `DisplayName` on the source object. The generated dockable `Context` stores the source object for template bindings.
When `ItemsSource` is set, Dock automatically creates dockables for each source item through the configured `IDockItemContainerGenerator`. With the default generator, generation requires the corresponding template (`DocumentTemplate` or `ToolTemplate`). The generated `Title` is derived from `Title`, `Name`, or `DisplayName` on the source object and `Context` stores the source object for template bindings.

`DockItemContainerGenerator` is the built-in default implementation. Subclass it or implement `IDockItemContainerGenerator` directly to customize container type, source-to-container mapping, or container cleanup behavior.

Changes to `INotifyCollectionChanged` collections (for example, `ObservableCollection<T>`) automatically add or remove corresponding dockables. When a generated document or tool is closed, the factory attempts to remove the source item from the collection if it implements `IList`.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Dock.Model.Avalonia.Controls;
using Dock.Model.Core;

namespace DockReactiveUIItemsSourceSample.Infrastructure;

public sealed class SampleDockItemContainerGenerator : DockItemContainerGenerator
{
public override IDockable? CreateDocumentContainer(IItemsSourceDock dock, object item, int index)
{
return new SampleGeneratedDocument
{
Id = $"SampleDocument-{index}"
};
}

public override void PrepareDocumentContainer(IItemsSourceDock dock, IDockable container, object item, int index)
{
base.PrepareDocumentContainer(dock, container, item, index);
container.Title = $"Doc {container.Title}";

if (container is SampleGeneratedDocument generatedDocument)
{
generatedDocument.SourceIndex = index;
}
}

public override IDockable? CreateToolContainer(IToolItemsSourceDock dock, object item, int index)
{
return new SampleGeneratedTool
{
Id = $"SampleTool-{index}"
};
}

public override void PrepareToolContainer(IToolItemsSourceDock dock, IDockable container, object item, int index)
{
base.PrepareToolContainer(dock, container, item, index);
container.Title = $"Tool {container.Title}";

if (container is SampleGeneratedTool generatedTool)
{
generatedTool.SourceIndex = index;
}
}
}

public sealed class SampleGeneratedDocument : Document
{
public int SourceIndex { get; set; } = -1;
}

public sealed class SampleGeneratedTool : Tool
{
public int SourceIndex { get; set; } = -1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ public MainWindowViewModel()
Documents.CollectionChanged += (_, _) => UpdateSummary();
Tools.CollectionChanged += (_, _) => UpdateSummary();

AddDocument("Welcome", "Welcome to the ReactiveUI ItemsSource sample.", "You can edit this text.");
AddDocument("Welcome", "Welcome to the ReactiveUI ItemsSource sample with custom container generation.", "You can edit this text.");
AddDocument("Notes", "Closing a generated document removes it from the source collection.", "Try the close button on tabs.");

AddTool("Explorer", "Source-backed tool generated via ToolDock.ItemsSource.");
AddTool("Explorer", "Source-backed tool generated via ToolDock.ItemsSource and custom container hooks.");
AddTool("Properties", "Another generated tool. Closing it updates the source collection.");

UpdateSummary();
Expand Down
13 changes: 11 additions & 2 deletions samples/DockReactiveUIItemsSourceSample/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
xmlns:dockModel="using:Dock.Model.Avalonia"
xmlns:dock="using:Dock.Avalonia.Controls"
xmlns:vm="using:DockReactiveUIItemsSourceSample.ViewModels"
xmlns:infra="using:DockReactiveUIItemsSourceSample.Infrastructure"
xmlns:sampleModels="using:DockReactiveUIItemsSourceSample.Models"
x:Class="DockReactiveUIItemsSourceSample.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Expand Down Expand Up @@ -44,11 +45,15 @@
Alignment="Left"
Proportion="0.3"
ItemsSource="{Binding DataContext.Tools, RelativeSource={RelativeSource AncestorType=Window}}">
<model:ToolDock.ItemContainerGenerator>
<infra:SampleDockItemContainerGenerator />
</model:ToolDock.ItemContainerGenerator>
<model:ToolDock.ToolTemplate>
<model:ToolTemplate>
<Border Padding="12" x:DataType="model:Tool">
<Border Padding="12" x:DataType="infra:SampleGeneratedTool">
<StackPanel Spacing="6">
<TextBlock Text="{Binding Title}" FontSize="16" FontWeight="SemiBold" />
<TextBlock Text="{Binding SourceIndex, StringFormat='Generated Source Index: {0}'}" Opacity="0.75" />
<StackPanel DataContext="{Binding Context}" x:DataType="sampleModels:ToolItem">
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" />
<TextBlock Text="{Binding Status}" Opacity="0.75" />
Expand All @@ -66,11 +71,15 @@
Proportion="0.7"
CanCreateDocument="False"
ItemsSource="{Binding DataContext.Documents, RelativeSource={RelativeSource AncestorType=Window}}">
<model:DocumentDock.ItemContainerGenerator>
<infra:SampleDockItemContainerGenerator />
</model:DocumentDock.ItemContainerGenerator>
<model:DocumentDock.DocumentTemplate>
<model:DocumentTemplate>
<Border Padding="12" x:DataType="model:Document">
<Border Padding="12" x:DataType="infra:SampleGeneratedDocument">
<StackPanel Spacing="6">
<TextBlock Text="{Binding Title}" FontSize="16" FontWeight="SemiBold" />
<TextBlock Text="{Binding SourceIndex, StringFormat='Generated Source Index: {0}'}" Opacity="0.75" />
<StackPanel DataContext="{Binding Context}" x:DataType="sampleModels:DocumentItem">
<TextBlock Text="{Binding Content}" TextWrapping="Wrap" />
<TextBox Text="{Binding EditableContent}" AcceptsReturn="True" MinHeight="120" TextWrapping="Wrap" />
Expand Down
Loading
Loading