This guide explains how to get started with Dock using ReactiveUI and dependency injection. This approach provides a clean separation of concerns, testability, and proper service management for complex applications.
The sample project DockReactiveUIDiSample
in the repository demonstrates this approach. For interface details refer to the Dock API Reference.
đź’ˇ Modern Approach: For easier document management, consider using DocumentDock.ItemsSource which automatically creates and manages documents from collections. The ItemsSource approach works seamlessly with dependency injection and ReactiveUI. This approach is covered in detail in the Document and Tool Content Guide.
Follow these instructions to create a ReactiveUI application with dependency injection using Dock.
Create a new Avalonia project
dotnet new avalonia.app -o MyDockApp
cd MyDockApp
Install the required packages
dotnet add package Dock.Avalonia
dotnet add package Dock.Model.ReactiveUI
dotnet add package Dock.Model.Extensions.DependencyInjection
dotnet add package Dock.Avalonia.Themes.Fluent
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Avalonia.ReactiveUI
Optional packages:
# For serialization (choose one):
dotnet add package Dock.Serializer.Newtonsoft # JSON (Newtonsoft.Json)
dotnet add package Dock.Serializer.SystemTextJson # JSON (System.Text.Json)
Set up Dependency Injection View Locator
Create a service-based ViewLocator that resolves views through the DI container:
using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Dock.Model.Core;
using ReactiveUI;
using Microsoft.Extensions.DependencyInjection;
namespace MyDockApp;
public class ViewLocator : IDataTemplate, IViewLocator
{
private readonly IServiceProvider _provider;
public ViewLocator(IServiceProvider provider)
{
_provider = provider;
}
private IViewFor? Resolve(object viewModel)
{
var vmType = viewModel.GetType();
var serviceType = typeof(IViewFor<>).MakeGenericType(vmType);
if (_provider.GetService(serviceType) is IViewFor view)
{
view.ViewModel = viewModel;
return view;
}
// Fallback: try to resolve by naming convention
var viewName = vmType.FullName?.Replace("ViewModel", "View");
if (viewName is not null)
{
var viewType = Type.GetType(viewName);
if (viewType != null && _provider.GetService(viewType) is IViewFor view2)
{
view2.ViewModel = viewModel;
return view2;
}
}
return null;
}
public Control? Build(object? data)
{
if (data is null)
return null;
if (Resolve(data) is IViewFor view && view is Control control)
return control;
var viewName = data.GetType().FullName?.Replace("ViewModel", "View");
return new TextBlock { Text = $"Not Found: {viewName}" };
}
public bool Match(object? data)
{
return data is ReactiveObject || data is IDockable;
}
IViewFor? IViewLocator.ResolveView<T>(T? viewModel, string? contract) where T : default =>
viewModel is null ? null : Resolve(viewModel);
}
Create your data models and services
Create a simple data service and model:
namespace MyDockApp.Models;
public class DemoData
{
public string Message { get; set; } = "Hello from Dependency Injection!";
public DateTime LastUpdated { get; set; } = DateTime.Now;
}
public interface IDataService
{
DemoData GetData();
void UpdateData(DemoData data);
}
public class DataService : IDataService
{
private DemoData _data = new();
public DemoData GetData() => _data;
public void UpdateData(DemoData data)
{
_data = data;
_data.LastUpdated = DateTime.Now;
}
}
Create view models using dependency injection
using System.Reactive.Disposables;
using ReactiveUI;
using Dock.Model.ReactiveUI.Controls;
using MyDockApp.Models;
namespace MyDockApp.ViewModels.Documents;
public class DocumentViewModel : Document
{
private readonly IDataService _dataService;
public DocumentViewModel(IDataService dataService)
{
_dataService = dataService;
var data = _dataService.GetData();
Content = data.Message;
// Set up reactive properties and commands as needed
this.WhenAnyValue(x => x.Content)
.Subscribe(content =>
{
var updatedData = new DemoData { Message = content };
_dataService.UpdateData(updatedData);
})
.DisposeWith(Disposables);
}
private string _content = string.Empty;
public string Content
{
get => _content;
set => this.RaiseAndSetIfChanged(ref _content, value);
}
}
namespace MyDockApp.ViewModels.Tools;
public class ToolViewModel : Tool
{
private readonly IDataService _dataService;
public ToolViewModel(IDataService dataService)
{
_dataService = dataService;
RefreshData();
}
private string _status = string.Empty;
public string Status
{
get => _status;
set => this.RaiseAndSetIfChanged(ref _status, value);
}
private void RefreshData()
{
var data = _dataService.GetData();
Status = $"Last updated: {data.LastUpdated:HH:mm:ss}";
}
}
Create a factory with dependency injection
using System;
using System.Collections.Generic;
using Dock.Model.Core;
using Dock.Model.ReactiveUI;
using Dock.Model.ReactiveUI.Controls;
using MyDockApp.Models;
using MyDockApp.ViewModels.Documents;
using MyDockApp.ViewModels.Tools;
using Microsoft.Extensions.DependencyInjection;
namespace MyDockApp.ViewModels;
public class DockFactory : Factory
{
private readonly IServiceProvider _provider;
public DockFactory(IServiceProvider provider)
{
_provider = provider;
}
public override IRootDock CreateLayout()
{
var document = _provider.GetRequiredService<DocumentViewModel>();
document.Id = "Document1";
document.Title = "Document";
var tool = _provider.GetRequiredService<ToolViewModel>();
tool.Id = "Tool1";
tool.Title = "Tool";
var proportionalDock = CreateProportionalDock();
proportionalDock.Orientation = Orientation.Horizontal;
proportionalDock.VisibleDockables = CreateList<IDockable>(
new DocumentDock
{
VisibleDockables = CreateList<IDockable>(document),
ActiveDockable = document
},
CreateProportionalDockSplitter(),
new ToolDock
{
VisibleDockables = CreateList<IDockable>(tool),
ActiveDockable = tool
});
var root = CreateRootDock();
root.VisibleDockables = CreateList<IDockable>(proportionalDock);
root.ActiveDockable = proportionalDock;
root.DefaultDockable = proportionalDock;
return root;
}
public override void InitLayout(IDockable layout)
{
DockableLocator = new Dictionary<string, Func<IDockable?>>
{
["Document1"] = () =>
{
var vm = _provider.GetRequiredService<DocumentViewModel>();
vm.Id = "Document1";
vm.Title = "Document";
return vm;
},
["Tool1"] = () =>
{
var vm = _provider.GetRequiredService<ToolViewModel>();
vm.Id = "Tool1";
vm.Title = "Tool";
return vm;
}
};
ContextLocator = new Dictionary<string, Func<object?>>
{
["Document1"] = () => _provider.GetRequiredService<DemoData>(),
["Tool1"] = () => _provider.GetRequiredService<DemoData>()
};
DefaultContextLocator = () => _provider.GetService(typeof(DemoData));
base.InitLayout(layout);
}
}
Create views
DocumentView.axaml:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyDockApp.Views.Documents.DocumentView">
<TextBox Text="{Binding Content}" AcceptsReturn="True" />
</UserControl>
DocumentView.axaml.cs:
using Avalonia.ReactiveUI;
using MyDockApp.ViewModels.Documents;
namespace MyDockApp.Views.Documents;
public partial class DocumentView : ReactiveUserControl<DocumentViewModel>
{
public DocumentView()
{
InitializeComponent();
}
}
ToolView.axaml:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyDockApp.Views.Tools.ToolView">
<StackPanel Margin="5">
<TextBlock Text="Tool Panel" FontWeight="Bold" />
<TextBlock Text="{Binding Status}" />
</StackPanel>
</UserControl>
ToolView.axaml.cs:
using Avalonia.ReactiveUI;
using MyDockApp.ViewModels.Tools;
namespace MyDockApp.Views.Tools;
public partial class ToolView : ReactiveUserControl<ToolViewModel>
{
public ToolView()
{
InitializeComponent();
}
}
Set up dependency injection in Program.cs
using System;
using Avalonia;
using Avalonia.ReactiveUI;
using Dock.Model.Extensions.DependencyInjection;
using Dock.Serializer;
using MyDockApp.Models;
using MyDockApp.ViewModels;
using MyDockApp.ViewModels.Documents;
using MyDockApp.ViewModels.Tools;
using MyDockApp.Views;
using MyDockApp.Views.Documents;
using MyDockApp.Views.Tools;
using ReactiveUI;
using Microsoft.Extensions.DependencyInjection;
namespace MyDockApp;
internal class Program
{
[STAThread]
private static void Main(string[] args)
{
using var provider = Initialize();
BuildAvaloniaApp(provider).StartWithClassicDesktopLifetime(args);
}
private static ServiceProvider Initialize()
{
var services = new ServiceCollection();
ConfigureServices(services);
return services.BuildServiceProvider();
}
private static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<App>();
services.AddSingleton<IViewLocator, ViewLocator>();
// Register data services
services.AddSingleton<DemoData>();
services.AddSingleton<IDataService, DataService>();
// Register view models
services.AddTransient<DocumentViewModel>();
services.AddTransient<ToolViewModel>();
services.AddSingleton<MainWindowViewModel>();
// Register views
services.AddTransient<IViewFor<DocumentViewModel>, DocumentView>();
services.AddTransient<IViewFor<ToolViewModel>, ToolView>();
services.AddTransient<IViewFor<MainWindowViewModel>, MainWindow>();
// Register Dock services
services.AddDock<DockFactory, DockSerializer>();
}
public static AppBuilder BuildAvaloniaApp(IServiceProvider provider)
=> AppBuilder.Configure(() => provider.GetRequiredService<App>())
.UsePlatformDetect()
.WithInterFont()
.UseReactiveUI()
.LogToTrace();
}
Update App.axaml.cs to use dependency injection
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using ReactiveUI;
using Microsoft.Extensions.DependencyInjection;
namespace MyDockApp;
public partial class App : Application
{
public IServiceProvider? ServiceProvider { get; }
private readonly IViewLocator _viewLocator;
public App()
{
}
public App(IServiceProvider? serviceProvider, IViewLocator viewLocator)
{
ServiceProvider = serviceProvider;
_viewLocator = viewLocator;
}
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
// Set the ReactiveUI view locator
Locator.CurrentMutable.RegisterConstant(_viewLocator, typeof(IViewLocator));
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var mainWindow = ServiceProvider?.GetRequiredService<MainWindow>();
if (mainWindow != null)
{
desktop.MainWindow = mainWindow;
}
}
base.OnFrameworkInitializationCompleted();
}
}
Run the application
dotnet run
Using dependency injection with Dock provides several advantages:
DisposeWith()
This approach is ideal for complex applications that require proper service management, testability, and clean architecture principles.