This guide highlights advanced features from the Dock samples. The API is shared across the MVVM, ReactiveUI and XAML versions so the same concepts apply no matter which approach you use. This guide assumes you are familiar with the basics from the other guides. Interface descriptions are available in the Dock API Reference. It focuses on runtime customization and advanced APIs.
All samples derive from Factory
and override methods to configure the layout. In addition to CreateLayout
, you can override:
CreateWindowFrom
to customize new floating windowsCreateDocumentDock
to provide a custom IDocumentDock
implementationInitLayout
to wire up ContextLocator
, DockableLocator
and HostWindowLocator
The MVVM and ReactiveUI samples use these hooks to register view models, window factories and other services required at runtime. By overriding the factory methods you can control how floating windows are created, inject your own tool or document types and tie into application specific services such as dependency injection containers.
public override void InitLayout(IDockable layout)
{
ContextLocator = new Dictionary<string, Func<object?>>
{
["Document1"] = () => new DemoDocument(),
["Tool1"] = () => new Tool1(),
// additional entries omitted
};
HostWindowLocator = new Dictionary<string, Func<IHostWindow?>>
{
[nameof(IDockWindow)] = () => new HostWindow()
};
base.InitLayout(layout);
}
FactoryBase
exposes events for virtually every docking action. The samples subscribe to them to trace runtime changes:
Events are useful for hooking into your application’s own logging or analytics system. For example you might record when documents are opened or closed so that the next run can restore them.
factory.ActiveDockableChanged += (_, args) =>
{
Debug.WriteLine($"[ActiveDockableChanged] {args.Dockable?.Title}");
};
factory.DockableAdded += (_, args) =>
{
Debug.WriteLine($"[DockableAdded] {args.Dockable?.Title}");
};
// Example: track created and active documents
factory.DockableAdded += (_, e) => Console.WriteLine($"Added {e.Dockable?.Id}");
factory.ActiveDockableChanged += (_, e) => Console.WriteLine($"Active {e.Dockable?.Id}");
You can react to focus changes, window moves or when dockables are pinned and unpinned.
The XAML sample demonstrates persisting layouts with DockSerializer
:
await using var stream = await file.OpenReadAsync();
var layout = _serializer.Load<IDock?>(stream);
if (layout is { })
{
dock.Layout = layout;
_dockState.Restore(layout);
}
DockState
tracks the active and focused dockables so the state can be restored after loading.
The Notepad sample shows how to create documents at runtime. New FileViewModel
instances are added to an IDocumentDock
using factory methods:
files.AddDocument(fileViewModel);
ToolDock
offers a matching AddTool
helper for dynamically created tools.
DocumentDock
includes a DocumentFactory
delegate that works with the
CreateDocument
command. Assigning this factory lets the dock create a
new document on demand which is then passed to AddDocument
and
activated automatically.
For more efficient document management, use the ItemsSource property to bind directly to data collections:
// In your ViewModel
public ObservableCollection<FileModel> OpenFiles { get; } = new();
// Add/remove documents by manipulating the collection
OpenFiles.Add(new FileModel { Title = "New File", Content = "..." });
OpenFiles.Remove(fileToClose);
With XAML binding:
<DocumentDock ItemsSource="{Binding OpenFiles}">
<DocumentDock.DocumentTemplate>
<DocumentTemplate>
<TextEditor Text="{Binding Context.Content}" x:DataType="Document"/>
</DocumentTemplate>
</DocumentDock.DocumentTemplate>
</DocumentDock>
This approach automatically creates and removes documents as the collection changes, provides automatic title mapping from properties like Title
or Name
, and integrates seamlessly with MVVM patterns. See the DocumentDock ItemsSource guide for comprehensive details.
Drag-and-drop handlers and file dialogs are used to open and save documents on the fly.
Calling FloatDockable
opens a dockable in a separate window. You can override CreateWindowFrom
to tweak the new window:
public override IHostWindow CreateWindowFrom(IDockWindow source)
{
var window = base.CreateWindowFrom(source);
window.Title = $"Floating - {source.Title}";
window.Width = 800;
window.Height = 600;
return window;
}
If EnableWindowDrag
on a DocumentDock
is set to true
, the tab strip doubles as a drag handle for the entire window. This lets users reposition floating windows by dragging the tabs themselves.
DockableBase
keeps track of several coordinate sets used while dragging or
pinning dockables. Methods like SetVisibleBounds
, SetPinnedBounds
and
SetTabBounds
store the latest position, whereas the matching Get*
methods
return the values. Two additional methods record the pointer location relative to
the dock control and to the screen.
// Record the bounds of a tool while it is pinned
tool.SetPinnedBounds(x, y, width, height);
// Retrieve the saved pointer location from the last drag
tool.GetPointerScreenPosition(out var screenX, out var screenY);
Override OnVisibleBoundsChanged
, OnPinnedBoundsChanged
, OnTabBoundsChanged
and the pointer variants if you need to react when these coordinates change,
for example to persist them or to show custom overlays.
Explore the samples under samples/
for complete implementations. Mixing these techniques with the basics lets you build complex layouts that can be persisted and restored.
If you are new to Dock, start with the MVVM Guide before diving into these topics.
For an overview of all guides see the documentation index.