This guide explains how to add actual content to your documents and tools in the Dock framework, addressing the most common questions about content setup.
Make sure you have the required NuGet packages installed:
<PackageReference Include="Dock.Avalonia" />
<PackageReference Include="Dock.Model.Avalonia" />
<PackageReference Include="Dock.Avalonia.Themes.Fluent" />
For XAML usage, you need these namespace declarations:
xmlns:dock="using:Dock.Model.Avalonia.Controls"
xmlns:dockCore="using:Dock.Model.Core"
The Dock framework supports four main approaches for defining content:
DocumentDock supports automatic document creation from collections using the ItemsSource
property, similar to how ItemsControl
works in Avalonia. This is the recommended approach for most scenarios.
This is ideal when you:
public class MyDocumentModel : INotifyPropertyChanged
{
private string _title = "";
private string _content = "";
private bool _canClose = true;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
public string Content
{
get => _content;
set => SetProperty(ref _content, value);
}
public bool CanClose
{
get => _canClose;
set => SetProperty(ref _canClose, value);
}
// INotifyPropertyChanged implementation...
public event PropertyChangedEventHandler? PropertyChanged;
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<MyDocumentModel> Documents { get; } = new();
public ICommand AddDocumentCommand { get; }
public ICommand RemoveDocumentCommand { get; }
public ICommand ClearAllCommand { get; }
public MainViewModel()
{
// Add some initial documents
Documents.Add(new MyDocumentModel
{
Title = "Welcome",
Content = "Welcome to the ItemsSource example!"
});
Documents.Add(new MyDocumentModel
{
Title = "Documentation",
Content = "This demonstrates automatic document creation from a collection."
});
AddDocumentCommand = new Command(AddNewDocument);
RemoveDocumentCommand = new Command(RemoveLastDocument, () => Documents.Count > 0);
ClearAllCommand = new Command(ClearAllDocuments, () => Documents.Count > 0);
}
private void AddNewDocument()
{
Documents.Add(new MyDocumentModel
{
Title = $"Document {Documents.Count + 1}",
Content = $"This is document number {Documents.Count + 1}",
CanClose = true
});
}
private void RemoveLastDocument()
{
if (Documents.Count > 0)
Documents.RemoveAt(Documents.Count - 1);
}
private void ClearAllDocuments()
{
Documents.Clear();
}
}
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:YourApp"
x:Class="YourApp.ItemsSourceExample">
<UserControl.DataContext>
<local:MainViewModel />
</UserControl.DataContext>
<Grid RowDefinitions="Auto,*">
<!-- Controls to add/remove items -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5">
<Button Content="Add Document" Command="{Binding AddDocumentCommand}" Margin="0,0,5,0" />
<Button Content="Remove Document" Command="{Binding RemoveDocumentCommand}" Margin="0,0,5,0" />
<Button Content="Clear All" Command="{Binding ClearAllCommand}" />
</StackPanel>
<DockControl Grid.Row="1" InitializeLayout="True" InitializeFactory="True">
<DockControl.Factory>
<Factory />
</DockControl.Factory>
<RootDock Id="Root" IsCollapsable="False">
<DocumentDock Id="DocumentsPane"
CanCreateDocument="True"
ItemsSource="{Binding Documents}">
<!-- Define how each document should be displayed -->
<DocumentDock.DocumentTemplate>
<DocumentTemplate>
<StackPanel Margin="10" x:DataType="Document">
<TextBlock Text="Document Title:" FontWeight="Bold"/>
<TextBox Text="{Binding Title}" Margin="0,0,0,10"/>
<TextBlock Text="Content:" FontWeight="Bold"/>
<TextBox Text="{Binding Context.Content}" AcceptsReturn="True" Height="200" TextWrapping="Wrap"/>
</StackPanel>
</DocumentTemplate>
</DocumentDock.DocumentTemplate>
</DocumentDock>
</RootDock>
</DockControl>
</Grid>
</UserControl>
Document
instanceTitle
→ Document titleName
→ Alternative for titleDisplayName
→ Alternative for titleCanClose
→ Whether the document can be closedContext
of the created Document and is accessible via {Binding Context.PropertyName}
The system automatically looks for common property names:
public class FileModel
{
public string Name { get; set; } = ""; // Used for document title
public string Path { get; set; } = "";
public bool IsReadOnly { get; set; }
public bool CanClose => !IsReadOnly; // Controls if document can be closed
}
<DocumentDock ItemsSource="{Binding OpenFiles}">
<DocumentDock.DocumentTemplate>
<DocumentTemplate>
<Grid RowDefinitions="Auto,*,Auto" x:DataType="Document">
<TextBlock Grid.Row="0" Text="{Binding Context.Path}" FontSize="12" Opacity="0.7"/>
<TextBox Grid.Row="1" Text="{Binding Context.Content}" AcceptsReturn="True"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Save" Command="{Binding Context.SaveCommand}" Margin="5"/>
<Button Content="Revert" Command="{Binding Context.RevertCommand}" Margin="5"/>
</StackPanel>
</Grid>
</DocumentTemplate>
</DocumentDock.DocumentTemplate>
</DocumentDock>
This approach follows MVVM principles and provides the best flexibility for complex scenarios.
using Dock.Model.Avalonia.Controls;
namespace YourApp.ViewModels.Documents;
public class TextDocumentViewModel : Document
{
private string _text = "";
public TextDocumentViewModel()
{
Id = Guid.NewGuid().ToString();
Title = "New Document";
CanClose = true;
}
public string Text
{
get => _text;
set => SetProperty(ref _text, value);
}
}
<!-- Views/Documents/TextDocumentView.axaml -->
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:YourApp.ViewModels.Documents"
x:Class="YourApp.Views.Documents.TextDocumentView"
x:DataType="vm:TextDocumentViewModel">
<Grid RowDefinitions="Auto,*">
<TextBlock Grid.Row="0" Text="{Binding Title}" FontWeight="Bold" Margin="5"/>
<TextBox Grid.Row="1" Text="{Binding Text}" AcceptsReturn="True" Margin="5"/>
</Grid>
</UserControl>
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:YourApp.ViewModels.Documents"
xmlns:views="using:YourApp.Views.Documents"
x:Class="YourApp.App">
<Application.DataTemplates>
<DataTemplate DataType="{x:Type vm:TextDocumentViewModel}">
<views:TextDocumentView />
</DataTemplate>
</Application.DataTemplates>
</Application>
private void AddNewDocument()
{
var existing = DocumentsPane.VisibleDockables
?.OfType<TextDocumentViewModel>()
?.FirstOrDefault(d => d.Id == "specific-id");
if (existing != null)
{
DocumentsPane.ActiveDockable = existing;
return;
}
// Create the document view model
var document = new TextDocumentViewModel
{
Title = "My Document",
Text = "Initial content"
};
// Add to dock
_factory?.AddDockable(DocumentsPane, document);
_factory?.SetActiveDockable(document);
_factory?.SetFocusedDockable(DocumentsPane, document);
}
If you need to create views dynamically or integrate with dependency injection:
private void AddDocumentWithFunction()
{
var document = new Document
{
Id = Guid.NewGuid().ToString(),
Title = "Function Document",
CanClose = true,
// Content is a function that creates the view
Content = new Func<IServiceProvider, object>(_ =>
{
// You can use DI container here if needed
var view = new TextDocumentView();
view.DataContext = new TextDocumentViewModel { Text = "Hello World" };
return view;
})
};
_factory?.AddDockable(DocumentsPane, document);
_factory?.SetActiveDockable(document);
}
For simple static content, you can define it directly in XAML:
<dock:DocumentDock x:Name="DocumentsPane" Id="DocumentsPane">
<dock:Document Id="WelcomeDoc" Title="Welcome" CanClose="false">
<StackPanel Margin="10">
<TextBlock Text="Welcome to the Application"
FontSize="18" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBlock Text="This is a welcome document with static content."
TextWrapping="Wrap"/>
<Button Content="Get Started" Margin="0,10,0,0"/>
</StackPanel>
</dock:Document>
<dock:Document Id="SettingsDoc" Title="Settings" CanClose="true">
<ScrollViewer>
<StackPanel Margin="10">
<TextBlock Text="Application Settings" FontWeight="Bold" Margin="0,0,0,10"/>
<CheckBox Content="Enable notifications" Margin="0,5"/>
<CheckBox Content="Auto-save documents" Margin="0,5"/>
<CheckBox Content="Dark theme" Margin="0,5"/>
</StackPanel>
</ScrollViewer>
</dock:Document>
</dock:DocumentDock>
Tools work similarly to documents. Here’s an example:
using Dock.Model.Avalonia.Controls;
namespace YourApp.ViewModels.Tools;
public class PropertiesToolViewModel : Tool
{
private object? _selectedObject;
public PropertiesToolViewModel()
{
Id = "PropertiesTool";
Title = "Properties";
CanClose = false;
}
public object? SelectedObject
{
get => _selectedObject;
set => SetProperty(ref _selectedObject, value);
}
}
<!-- Views/Tools/PropertiesToolView.axaml -->
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:YourApp.ViewModels.Tools"
x:Class="YourApp.Views.Tools.PropertiesToolView"
x:DataType="vm:PropertiesToolViewModel">
<Grid>
<TextBlock Text="Properties Panel" VerticalAlignment="Center"
HorizontalAlignment="Center" FontStyle="Italic"
IsVisible="{Binding SelectedObject, Converter={x:Static ObjectConverters.IsNull}}"/>
<ScrollViewer IsVisible="{Binding SelectedObject, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Margin="5">
<TextBlock Text="Selected Object Properties" FontWeight="Bold" Margin="0,0,0,10"/>
<!-- Add property editors here -->
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
<Application.DataTemplates>
<DataTemplate DataType="{x:Type vm:PropertiesToolViewModel}">
<views:PropertiesToolView />
</DataTemplate>
</Application.DataTemplates>
Problem: Getting System.ArgumentException: "Unexpected content YourView"
when adding documents.
Cause: Setting a UserControl instance directly to the Content
property.
Solution: Use one of the supported approaches above (ViewModel pattern or function-based content).
// ❌ This will cause "Unexpected content" error
var document = new Document
{
Content = new MyUserControl() // Don't do this
};
// ✅ Use ViewModel approach instead
var document = new MyDocumentViewModel();
// ✅ Or use function approach
var document = new Document
{
Content = new Func<IServiceProvider, object>(_ => new MyUserControl())
};
Problem: Document tabs show up but content is empty when using ItemsSource.
Solutions:
DocumentTemplate
has proper x:DataType="Document"
on the root element{Binding Context.PropertyName}
not {Binding PropertyName}
INotifyPropertyChanged
Example Fix:
<!-- ❌ Wrong DataType -->
<DocumentTemplate x:DataType="local:MyModel">
<TextBlock Text="{Binding Title}"/>
</DocumentTemplate>
<!-- ✅ Correct DataType -->
<DocumentTemplate>
<StackPanel x:DataType="Document">
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Context.Content}"/>
</StackPanel>
</DocumentTemplate>
Problem: Document tabs show up but content is empty when using ViewModel approach.
Solutions:
App.axaml
x:DataType
matches the ViewModel typeProblem: Unable to resolve type RootDock from namespace https://github.com/avaloniaui
Solution: Add the Dock.Model.Avalonia
package and namespace:
xmlns:dock="using:Dock.Model.Avalonia.Controls"
Problem: Changes to your model properties don’t reflect in the document content.
Solution: Ensure your model implements INotifyPropertyChanged
:
public class MyDocumentModel : INotifyPropertyChanged
{
private string _title = "";
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
public event PropertyChangedEventHandler? PropertyChanged;
}
This example shows a complete file manager implementation using the ItemsSource approach:
// File Model
public class FileDocument : INotifyPropertyChanged
{
private string _title = "";
private string _content = "";
private string _filePath = "";
private bool _isModified;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
public string Content
{
get => _content;
set
{
if (SetProperty(ref _content, value))
{
IsModified = true;
}
}
}
public string FilePath
{
get => _filePath;
set => SetProperty(ref _filePath, value);
}
public bool IsModified
{
get => _isModified;
set => SetProperty(ref _isModified, value);
}
public bool CanClose => !IsModified || ConfirmClose();
public ICommand SaveCommand { get; }
public ICommand SaveAsCommand { get; }
public FileDocument()
{
SaveCommand = new Command(Save, () => IsModified);
SaveAsCommand = new Command(SaveAs);
}
private void Save()
{
// Save to FilePath
File.WriteAllText(FilePath, Content);
IsModified = false;
}
private void SaveAs()
{
// Show save dialog and save
}
private bool ConfirmClose()
{
// Show "Save changes?" dialog
return true; // or false based on user choice
}
// INotifyPropertyChanged implementation...
}
// Main ViewModel
public class FileManagerViewModel : INotifyPropertyChanged
{
public ObservableCollection<FileDocument> OpenFiles { get; } = new();
public ICommand OpenFileCommand { get; }
public ICommand NewFileCommand { get; }
public FileManagerViewModel()
{
OpenFileCommand = new Command(OpenFile);
NewFileCommand = new Command(NewFile);
}
private void NewFile()
{
OpenFiles.Add(new FileDocument
{
Title = $"Untitled{OpenFiles.Count + 1}.txt",
Content = "",
FilePath = ""
});
}
private void OpenFile()
{
// Show file dialog and load file
var filePath = ShowOpenFileDialog();
if (!string.IsNullOrEmpty(filePath))
{
var content = File.ReadAllText(filePath);
OpenFiles.Add(new FileDocument
{
Title = Path.GetFileName(filePath),
Content = content,
FilePath = filePath
});
}
}
}
XAML:
<DockControl>
<DockControl.Factory>
<Factory />
</DockControl.Factory>
<RootDock>
<DocumentDock ItemsSource="{Binding OpenFiles}">
<DocumentDock.DocumentTemplate>
<DocumentTemplate>
<Grid RowDefinitions="Auto,*,Auto" x:DataType="Document">
<!-- File path header -->
<TextBlock Grid.Row="0" Text="{Binding Context.FilePath}"
FontSize="10" Opacity="0.7" Margin="5"/>
<!-- Main content editor -->
<TextBox Grid.Row="1" Text="{Binding Context.Content}"
AcceptsReturn="True" AcceptsTab="True"
FontFamily="Consolas" Margin="5"/>
<!-- Action buttons -->
<StackPanel Grid.Row="2" Orientation="Horizontal"
HorizontalAlignment="Right" Margin="5">
<Button Content="Save" Command="{Binding Context.SaveCommand}"
IsEnabled="{Binding Context.IsModified}"/>
<Button Content="Save As" Command="{Binding Context.SaveAsCommand}"
Margin="5,0,0,0"/>
</StackPanel>
</Grid>
</DocumentTemplate>
</DocumentDock.DocumentTemplate>
</DocumentDock>
</RootDock>
</DockControl>
// ViewModel
public class TextEditorViewModel : Document
{
private string _content = "";
private bool _isModified;
public string Content
{
get => _content;
set
{
if (SetProperty(ref _content, value))
{
IsModified = true;
OnPropertyChanged(nameof(DisplayTitle));
}
}
}
public bool IsModified
{
get => _isModified;
set => SetProperty(ref _isModified, value);
}
public string DisplayTitle => IsModified ? $"{Title}*" : Title;
public void Save()
{
// Save logic here
IsModified = false;
OnPropertyChanged(nameof(DisplayTitle));
}
}
// Tool ViewModel
public class ObjectInspectorViewModel : Tool
{
private object? _target;
private PropertyInfo[]? _properties;
public object? Target
{
get => _target;
set
{
if (SetProperty(ref _target, value))
{
Properties = value?.GetType().GetProperties();
}
}
}
public PropertyInfo[]? Properties
{
get => _properties;
set => SetProperty(ref _properties, value);
}
}
This comprehensive guide should help users understand how to properly work with document and tool content in the Dock framework, with the new ItemsSource functionality providing the most streamlined approach for most scenarios.