DocumentDock and ToolDock ItemsSource
The DocumentDock.ItemsSource and ToolDock.ItemsSource properties enable automatic dockable management by binding collections of data objects. This provides a clean, MVVM-friendly approach for both document tabs and tool panes.
Overview
DocumentDock.ItemsSource and ToolDock.ItemsSource work similarly to ListBox.ItemsSource in Avalonia. When you add or remove items from an INotifyCollectionChanged collection (such as ObservableCollection<T>), corresponding dockables are created or removed automatically.
ItemsSource is implemented by Dock.Model.Avalonia.Controls.DocumentDock and Dock.Model.Avalonia.Controls.ToolDock in the Avalonia model layer:
- By default,
DocumentDock.ItemsSourceusesDocumentTemplatefor generated content. - By default,
ToolDock.ItemsSourceusesToolTemplatefor generated content. DocumentDock.ItemContainerGeneratorandToolDock.ItemContainerGeneratorlet you override container creation and preparation.DocumentDock.DocumentItemContainerThemeandToolDock.ToolItemContainerThemelet you apply per-dock container theme metadata for generated dockables.DocumentDock.DocumentItemTemplateSelectorandToolDock.ToolItemTemplateSelectorlet you choose per-item template content while preserving template fallback behavior.
With the default generator, generated dockables are created when either:
- the dock template is set (
DocumentTemplate/ToolTemplate), or - a template selector is set and returns content for the item.
A custom ItemContainerGenerator can fully override this behavior.
Behavior details
- By default,
DocumentDockcreates aDocumentfor each item and stores the item inDocument.Context. - By default,
ToolDockcreates aToolfor each item and stores the item inTool.Context. - The tab title is derived from
Title,Name, orDisplayNameproperties on the item (in that order), falling back toToString(). CanCloseis copied from the item if present; otherwise it defaults totrue.- A custom container generator can map per-item capability metadata into
IDockable.DockCapabilityOverridesfor generated windows. IFactory.GetContainerFromItem(object item)returns the currently tracked generated container for a source item.- When a generated document or tool is closed, Dock can remove the source item from
ItemsSource(when it implementsIList), controlled byDockSettings.UpdateItemsSourceOnUnregisterand per-dock overrides. - Source-generated document/tool closes are treated as remove operations, even when
Factory.HideDocumentsOnCloseorFactory.HideToolsOnCloseis enabled. - You can replace the default generation pipeline with
IDockItemContainerGeneratorfor custom container types, metadata mapping, and cleanup. - Theme metadata from
DocumentItemContainerTheme/ToolItemContainerThemeis copied to generated containers and applied byDocumentContentControl/ToolContentControl. - Template selectors run before dock templates. If a selector returns
null, Dock falls back toDocumentTemplate/ToolTemplate. - Changing
DocumentTemplate,ToolTemplate, per-dock theme metadata, or selector regenerates source-generated containers.
Container Lookup and Unregister Policy
Use IFactory.GetContainerFromItem to resolve the generated dock container from a source item:
var sourceItem = viewModel.Documents[0];
var container = factory.GetContainerFromItem(sourceItem);
if (container is IDocument document)
{
factory.SetActiveDockable(document);
}
Control whether closing a generated container removes the source item:
- Global default:
DockSettings.UpdateItemsSourceOnUnregister(defaulttrue) - Per-document dock override:
DocumentDock.CanUpdateItemsSourceOnUnregister(bool?,null= global) - Per-tool dock override:
ToolDock.CanUpdateItemsSourceOnUnregister(bool?,null= global)
<DocumentDock ItemsSource="{Binding Documents}"
CanUpdateItemsSourceOnUnregister="False" />
<ToolDock ItemsSource="{Binding Tools}"
CanUpdateItemsSourceOnUnregister="{Binding ToolUnregisterPolicy}" />
Per-Dock Theme and Template Selector APIs
The default generator now supports per-dock presentation customization for generated items:
DocumentDock.DocumentItemContainerThemeToolDock.ToolItemContainerThemeDocumentDock.DocumentItemTemplateSelector(IDocumentItemTemplateSelector)ToolDock.ToolItemTemplateSelector(IToolItemTemplateSelector)
DocumentItemContainerTheme / ToolItemContainerTheme accept either:
- a
ControlThemeinstance, or - a resource key that resolves to a
ControlTheme.
Template selectors return template content for an item. They can return:
null(use dock template fallback),- a
Func<IServiceProvider, object>, - a
Controlinstance, - a
DocumentTemplate/ToolTemplate, - any content object supported by Dock template loading.
Example
<Window.Resources>
<local:MyDocumentSelector x:Key="DocumentSelector" />
<ControlTheme x:Key="GeneratedDocumentTheme"
TargetType="dock:DocumentContentControl"
BasedOn="{StaticResource {x:Type dock:DocumentContentControl}}">
<Setter Property="Margin" Value="3" />
</ControlTheme>
</Window.Resources>
<DocumentDock ItemsSource="{Binding Documents}"
DocumentItemContainerTheme="GeneratedDocumentTheme"
DocumentItemTemplateSelector="{StaticResource DocumentSelector}">
<DocumentDock.DocumentTemplate>
<DocumentTemplate>
<!-- fallback template for items not handled by selector -->
<TextBlock Text="{Binding Context.Title}" />
</DocumentTemplate>
</DocumentDock.DocumentTemplate>
</DocumentDock>
Migration Path
- Existing
ItemsSource + DocumentTemplate/ItemsSource + ToolTemplatebehavior is unchanged. - Add selectors only where you need per-item template switching.
- Add per-dock container themes only where generated item hosts need distinct styling.
- Existing custom
IDockItemContainerGeneratorimplementations continue to work unchanged.
Key Benefits
- Automatic Document Management: Documents are created and removed automatically when the collection changes
- MVVM Support: Natural binding to
ObservableCollection<T>in ViewModels - Clean Separation: Business models remain separate from UI infrastructure
- Simplified Code: Eliminates manual document creation and management boilerplate
Basic Usage
1. Document Model
Create a model class to represent your document data:
public class FileDocument : INotifyPropertyChanged
{
private string _title = "";
private string _content = "";
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
public string Content
{
get => _content;
set => SetProperty(ref _content, value);
}
public bool CanClose { get; set; } = true;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
2. ViewModel Setup
Create a ViewModel with an ObservableCollection of your document models:
The example below uses RelayCommand from CommunityToolkit.Mvvm.Input. Replace it with any ICommand implementation you prefer.
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<FileDocument> Documents { get; } = new();
public ICommand AddDocumentCommand { get; }
public ICommand RemoveDocumentCommand { get; }
public MainViewModel()
{
AddDocumentCommand = new RelayCommand(AddDocument);
RemoveDocumentCommand = new RelayCommand<FileDocument>(RemoveDocument);
// Add some initial documents
Documents.Add(new FileDocument
{
Title = "Welcome.txt",
Content = "Welcome to the application!"
});
}
private void AddDocument()
{
Documents.Add(new FileDocument
{
Title = $"Document {Documents.Count + 1}.txt",
Content = "New document content"
});
}
private void RemoveDocument(FileDocument document)
{
Documents.Remove(document);
}
}
3. XAML Binding
Bind the collection to DocumentDock.ItemsSource and define a DocumentTemplate:
<DocumentDock ItemsSource="{Binding Documents}">
<DocumentDock.DocumentTemplate>
<DocumentTemplate>
<StackPanel Margin="10" x:DataType="Document">
<TextBlock Text="Document Properties" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBlock Text="Title:" FontWeight="SemiBold"/>
<TextBox Text="{Binding Context.Title}" Margin="0,0,0,10"/>
<TextBlock Text="Content:" FontWeight="SemiBold"/>
<TextBox Text="{Binding Context.Content}"
AcceptsReturn="True"
TextWrapping="Wrap"
Height="200"/>
</StackPanel>
</DocumentTemplate>
</DocumentDock.DocumentTemplate>
</DocumentDock>
Property Mapping
The system automatically maps properties from your model to Document properties:
| Model Property | Document Property | Description |
|---|---|---|
Title |
Title |
Tab title display |
Name |
Title |
Alternative to Title |
DisplayName |
Title |
Alternative to Title |
CanClose |
CanClose |
Controls if tab can be closed |
Example with Multiple Property Options
public class DocumentModel
{
// Any of these will be used for the tab title (in order of preference):
public string Title { get; set; } // First choice
public string Name { get; set; } // Second choice
public string DisplayName { get; set; } // Third choice
// Controls whether the tab can be closed
public bool CanClose { get; set; } = true;
}
Document Template Context
DocumentTemplate is built with the generated Document as its data context, so bind to document properties directly and use Context to reach the source model:
<DocumentTemplate>
<Grid x:DataType="Document">
<!-- Access your model properties via Context -->
<TextBlock Text="{Binding Context.Title}"/>
<TextBox Text="{Binding Context.Content}"/>
<!-- You can also access Document properties directly -->
<TextBlock Text="{Binding Title}"/>
</Grid>
</DocumentTemplate>
Compiled bindings + Context: When using compiled bindings,
Contextisobject?onDocument, so{Binding Context.SomeProperty}can fail to compile. Rebind a subtree to the model and set a concretex:DataType, or cast in the binding path:<DocumentTemplate> <StackPanel x:DataType="Document"> <StackPanel DataContext="{Binding Context}" x:DataType="models:FileDocument"> <TextBox Text="{Binding Content}"/> </StackPanel> </StackPanel> </DocumentTemplate>
Common Use Cases
File Editor
public class FileModel : INotifyPropertyChanged
{
public string Name { get; set; } // Becomes tab title
public string FilePath { get; set; }
public string Content { get; set; }
public bool IsDirty { get; set; }
public bool IsReadOnly { get; set; }
public bool CanClose => !IsDirty; // Prevent closing unsaved files
public ICommand SaveCommand { get; }
public ICommand RevertCommand { get; }
}
Settings Panel
public class SettingsPage : INotifyPropertyChanged
{
public string Title { get; set; } // Tab title
public string Category { get; set; }
public string Icon { get; set; }
public Dictionary<string, object> Settings { get; set; } = new();
public bool CanClose { get; set; } = true;
}
Data Viewer
public class DataView : INotifyPropertyChanged
{
public string DisplayName { get; set; } // Tab title
public ObservableCollection<object> Data { get; set; } = new();
public string Filter { get; set; } = "";
public bool CanClose { get; set; } = true;
public ICommand RefreshCommand { get; }
public ICommand ExportCommand { get; }
}
Advanced Scenarios
ToolDock Usage
Bind a tools collection to ToolDock.ItemsSource and define a ToolTemplate:
<ToolDock Alignment="Left" ItemsSource="{Binding Tools}">
<ToolDock.ToolTemplate>
<ToolTemplate>
<StackPanel Margin="10" x:DataType="Tool">
<TextBlock Text="{Binding Title}" FontWeight="Bold"/>
<StackPanel DataContext="{Binding Context}" x:DataType="models:ToolItem">
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"/>
<TextBlock Text="{Binding Status}" Opacity="0.75"/>
</StackPanel>
</StackPanel>
</ToolTemplate>
</ToolDock.ToolTemplate>
</ToolDock>
Tool Property Mapping
Generated Tool instances use the same property mapping strategy:
| Model Property | Tool Property | Description |
|---|---|---|
Title |
Title |
Tool tab title |
Name |
Title |
Alternative title source |
DisplayName |
Title |
Alternative title source |
CanClose |
CanClose |
Controls whether the tool can be closed |
Dynamic Content Types
public class DynamicDocument : INotifyPropertyChanged
{
public string Title { get; set; }
public DocumentType Type { get; set; }
public object Data { get; set; }
public bool CanClose { get; set; } = true;
}
public enum DocumentType
{
Text,
Image,
Chart,
Table
}
<DocumentDock ItemsSource="{Binding Documents}">
<DocumentDock.DocumentTemplate>
<DocumentTemplate>
<ContentControl x:DataType="Document">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource DocumentTypeConverter}">
<Binding Path="Context.Type"/>
<Binding Path="Context.Data"/>
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</DocumentTemplate>
</DocumentDock.DocumentTemplate>
</DocumentDock>
Custom Container Generator
Use IDockItemContainerGenerator when you need custom container types or custom preparation/cleanup logic for source-generated dockables.
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:
<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:
CreateDocumentContainershould return anIDocumentimplementation (for exampleDocumentor a derived type).CreateToolContainershould return anIToolimplementation (for exampleToolor a derived type).
Incompatible container types are skipped by the pipeline and immediately cleared.
Custom Commands Integration
public class CommandDocument : INotifyPropertyChanged
{
public string Title { get; set; }
public ICommand CloseCommand { get; }
public ICommand SaveCommand { get; }
public bool CanClose { get; set; } = true;
public CommandDocument()
{
CloseCommand = new RelayCommand(() => {
// Custom close logic
var parent = GetParentCollection();
parent?.Remove(this);
});
SaveCommand = new RelayCommand(Save);
}
private void Save()
{
// Save logic
}
}
Best Practices
- Implement INotifyPropertyChanged: Ensures UI updates when model properties change
- Use ObservableCollection: Automatically notifies the UI of collection changes
- Keep Models Simple: Avoid complex UI logic in document models
- Meaningful Titles: Provide clear, descriptive titles for tabs
- Handle CanClose: Use this property to prevent accidental data loss
- Consider Commands: Implement actions as commands for better MVVM support
- Data Validation: Validate model properties to ensure data integrity
Performance Considerations
- Large Collections: Consider virtualization for collections with many items
- Frequent Updates: Batch collection changes when possible
- Memory Management: Ensure proper cleanup of resources in document models
- Template Complexity: Keep
DocumentTemplateandToolTemplatelightweight for better performance
Troubleshooting
Common Issues
Documents not appearing:
- Ensure
ItemsSourceis properly bound - Check that the collection implements
INotifyCollectionChanged(useObservableCollection) - Verify
DocumentTemplateis defined
Title not showing:
- Check that your model has a
Title,Name, orDisplayNameproperty - Ensure the property is public and has a getter
Templates not updating:
- Implement
INotifyPropertyChangedin your model - Use proper binding syntax with
Context.PropertyName
Tabs not closable:
- Set
CanClose = truein your model (default behavior) - Check that the property is accessible
See Also
- Document and Tool Content Guide - Comprehensive content setup
- Dock Advanced Topics - Advanced docking scenarios
- Dock FAQ - Common questions and troubleshooting
samples/DockXamlReactiveUISample- ReactiveUI sample using both document and tool items sources