DocumentDock ItemsSource
The DocumentDock.ItemsSource property enables automatic document management by binding to a collection of data objects. This functionality provides a clean, MVVM-friendly approach to managing documents within a dock.
Overview
DocumentDock.ItemsSource works similarly to ListBox.ItemsSource in Avalonia, automatically creating and managing documents from a bound collection. When you add or remove items from an INotifyCollectionChanged collection (such as ObservableCollection<T>), the corresponding documents are created or removed in the dock.
ItemsSource is implemented by Dock.Model.Avalonia.Controls.DocumentDock, so it is available in XAML layouts and in the Avalonia model layer. It requires a DocumentTemplate to be set; if no template is supplied, no documents are generated.
Behavior details
DocumentDockcreates aDocumentfor each item and stores the item inDocument.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.- When a generated document is closed, the factory attempts to remove the source item from
ItemsSourceif it implementsIList.
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
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 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
DocumentTemplatelightweight 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