AvalonDock compatibility
Avalonia compatibility adapter: the package and sample use official Avalonia; the sample is included in the normal solution and focused headless CI tests. Full WPF framework parity, the historical parity suite, generated resource factories, and API metadata remain deferred in issue #1186. Full-parity results below apply to the historical custom-framework revision.
Run the sample from the repository root:
dotnet run --project samples/DockAvalonDockSample
The current source uses Avalonia property and binding semantics, public logical
projections, ordinary logical parenting for native roots, model-ready floating
initialization, and capture through pointer events. These paths support the
sample without a framework fork; exact WPF binding ownership, weak inheritance,
native-host lifecycle, and global capture remain upstream work. Compiled resource
catalogs still need the upstream factory API; use explicit typed registrations
in the meantime. Catalog generation defaults to false; applications without
custom resource dictionaries can enable layout catalogs with
<AvalonDockGenerateCatalog>true</AvalonDockGenerateCatalog> and a partial
Application class.
Dock.Avalonia.AvalonDock lets an Avalonia application use the
AvalonDock C# namespaces and layout names. The implementation wraps
Dock.Model.Avalonia models and renders them through Dock's controls. The
acceptance checklist
records implementation evidence, framework differences, and deferred desktop
checks. WPF controls, dependency properties, and assemblies require their
Avalonia counterparts; exact WPF behavior and binary compatibility are not claimed.
The CLR namespaces are AvalonDock.*; update using directives and any explicit
CLR namespace declarations when porting source from the WPF library. The
assembly/package name remains Dock.Avalonia.AvalonDock.
The prototype required project-specific framework APIs. The framework history records that dependency and links to the preserved implementation. Dock's normal restore, build, test, and release flows no longer require custom framework packages.
Runtime source
77c15f965
passed 3,954 adapter tests, 30 rendering tests, and isolated package consumers on
.NET 8 and .NET 10. Six catalog integration cases and 18 generator cases also
passed. That validation used framework source
57744b19b6.
The acceptance checklist above records the validation scope and deferred desktop
checks.
Floating hosts default to ShowInTaskbar="False". Overlay windows default to
no activation, no taskbar entry, no decorations, and a transparent surface.
These are property defaults and apply before styling and with a replacement
control theme. Application styles and local values can override them, and
clearing those values restores the defaults. LayoutGridResizerControl likewise
has a transparent background without relying on its theme. Auto-hide previews
default to focusable and hidden; application focus styles remain effective.
Layout content starts with zero floating dimensions, letting Dock choose the native window size unless an application supplies one. Document-list sorting uses comparable application content when available and falls back to titles. XML return destinations preserve explicit empty pane identifiers and are resolved before content-recovery callbacks; an unset identifier is distinct from an empty one. Return-container assignments notify before initializing an unset pane ID, while identifier metadata itself does not raise property notifications.
Reference the package and theme
The package targets .NET 8 and .NET 10 and uses official Avalonia packages:
dotnet add package Dock.Avalonia.AvalonDock
Its analyzer and build targets are included automatically. Package-consumer tests validate XAML, themes, XML layout persistence, and opt-in layout catalogs before CI or release artifacts are uploaded. Full WPF parity remains tracked in #1186.
For historical reproduction only, use the validated revision's build guide in a separate checkout. Those instructions and custom packages do not apply to the current Dock build.
In App.axaml, with the default xmlns="https://github.com/avaloniaui", add:
<Application.Styles>
<FluentTheme />
<AvalonDockTheme />
</Application.Styles>
AvalonDockTheme includes Dock's Fluent resources and the manager/content
templates that connect the compatibility API to Dock's controls.
Declare a layout in XAML
Place this layout inside an Avalonia view. Declare x:DataType on the view for
its ViewModel when using compiled bindings. This example uses literal content:
<DockingManager x:Name="DockManager">
<LayoutRoot>
<LayoutPanel Orientation="Horizontal">
<LayoutAnchorablePane DockWidth="240">
<LayoutAnchorable ContentId="properties" Title="Properties">
<TextBlock Text="Workspace properties" Margin="12" />
</LayoutAnchorable>
</LayoutAnchorablePane>
<LayoutDocumentPane>
<LayoutDocument ContentId="readme" Title="Readme">
<TextBox Text="Editable document" AcceptsReturn="True" />
</LayoutDocument>
</LayoutDocumentPane>
</LayoutPanel>
</LayoutRoot>
</DockingManager>
The assembly maps the manager, layout, and theme namespaces to Avalonia's default XML namespace, so these elements require no AvalonDock prefix. The Xceed XML namespace is also supported:
<Window xmlns="https://github.com/avaloniaui"
xmlns:ad="http://schemas.xceed.com/wpf/xaml/avalondock">
<ad:DockingManager>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPane>
<ad:LayoutDocument Title="Document">
<TextBlock Text="Avalonia content" />
</ad:LayoutDocument>
</ad:LayoutDocumentPane>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
</Window>
ad is an arbitrary alias. Using a prefix can make the library boundary
explicit in a mixed XAML file; it does not change the layout's behavior.
The original WPF library maps its types to the Xceed URI and suggests xcad
in its assembly namespace declarations.
A prefix lets a WPF view keep the usual WPF presentation namespace as its
default. Unprefixed AvalonDock elements are also possible by making the Xceed
URI the default on that subtree; WPF controls inside it then need their own
prefix or a reset default namespace. This adapter additionally maps to
Avalonia's default namespace, so the first example can mix unprefixed
AvalonDock and Avalonia elements directly.
Drag previews
Full-content drag previews are enabled by default. Dragging a document, tool, or
tool pane uses Dock's existing preview window or managed overlay and a snapshot of
its registered content visual, including application templates. Capturing before
the source switches tabs keeps the dragged editor visible; native code releases
the image when dragging ends. Document previews use the editor's
dimensions. To use a compact caption/status preview, set the Avalonia extension
ShowDockablePreviewOnDrag="False" on the manager. The setting applies to docked
and floating content at the start of each drag; other managers and Dock's global
DockSettings.ShowDockablePreviewOnDrag setting remain independent.
The source also updates as soon as dragging begins. A dragged tab disappears and its pane shows a remaining tab. Dragging a whole tool pane or its last tool closes the vacated grid space, including empty nested tool groups. Document panes inside a document group also vacate their space; a main document region remains available as an empty drop target. Both split orientations use the same behavior. This source presentation applies to both full-content and compact previews.
Dock owns the drag lifetime through IDockDragSourcePreviewProvider. The adapter
temporarily overrides grid visibility and sizes; it does not remove live content
or rewrite saved proportions. Escape, capture loss, and host detachment restore
presentation. The scope ends before a successful drop changes ownership, and
application size changes made during the drag survive cleanup.
Original WPF AvalonDock starts a floating window when the pointer leaves the
tab strip's drag buffer. Its CreateFloatingWindowCore removes content from the
source pane before showing that window, and its whole-tool-pane path moves every
child into a new pane. See the pinned upstream
tab drag code
and floating creation.
The adapter matches the visible source removal while retaining Dock's reversible
preview. Public parent/collection changes and floating events therefore occur
on drop, rather than at drag start; this is an explicit timing difference.
<DockingManager ShowDockablePreviewOnDrag="True">
<LayoutRoot>
<LayoutPanel>
<LayoutDocumentPane>
<LayoutDocument Title="Preview this editor">
<TextBox Text="Drag the tab to see the full editor" />
</LayoutDocument>
</LayoutDocumentPane>
</LayoutPanel>
</LayoutRoot>
</DockingManager>
Use the C# layout API
The following code belongs in a workspace service called by ViewModel commands.
manager is the DockingManager declared above, supplied by application startup.
The sample uses an ILayoutWorkspace interface so its ViewModel has no Avalonia
dependency and its view code-behind only initializes XAML.
using System.Linq;
using AvalonDock.Layout;
using AvalonDock.Layout.Serialization;
LayoutDocument document = manager.Layout.Descendents()
.OfType<LayoutDocument>().Single(item => item.ContentId == "readme");
LayoutAnchorable properties = manager.Layout.Descendents()
.OfType<LayoutAnchorable>().Single(item => item.ContentId == "properties");
document.IsActive = true;
document.Float();
document.Dock();
properties.Hide();
properties.Show();
properties.ToggleAutoHide();
new XmlLayoutSerializer(manager).Serialize("layout.xml");
new XmlLayoutSerializer(manager).Deserialize("layout.xml");
Content methods operate through the native Dock factory. Closing and hiding honor the corresponding capabilities and cancelable events. A tool's return pane and tab position are retained when it floats, docks, hides, or is restored. After deserialization, look up layout nodes again: the manager now owns a new layout tree, while content can retain its application identity.
manager.Layout.ActiveContent is the public document or tool node, including
nodes in floating windows. manager.ActiveContent retains an explicitly assigned
object; activation from the layout publishes the layout node, as in WPF AvalonDock:
document.Float();
manager.ActiveContent = document.Content; // Retains the application object.
manager.Layout.ActiveContent = document;
LayoutContent? activeNode = manager.Layout.ActiveContent;
object? applicationContent = activeNode?.Content;
Activation through either property, IsActive, a container command, or the
native Dock factory uses the owning native Dock root. Each native root retains
its own focused item. A layout assignment that does not change the active node
does not republish the manager value. Replacing Layout also leaves the manager
value intact until a subsequent activation change.
Manager lookup uses the first matching item: an application's object matches
Content by identity; a supplied layout node first uses its non-null Content,
then its non-null ContentId, then an empty-content match. ContentId initially
is null. Give distinct empty items distinct identifiers. Assigning null can
activate the first empty item; it is not an unconditional deactivation operation.
ActiveContentProperty supports styles and defaults to one-way binding. Specify
Mode=TwoWay when layout activation should update a bound ViewModel. The protected
OnActiveContentChanged hook runs after the layout update; its base implementation
raises ActiveContentChanged. Source updates follow the binding's configured
trigger, including explicit updates.
The adapter opts these properties into a reflection-free framework value policy:
| Operation | Compatibility behavior |
|---|---|
| Assign over a one-way/default local binding | Replaces the expression, including an equal assignment. |
| Assign over a two-way binding | Retains the expression and follows its source-update trigger. |
SetCurrentValue |
Retains the expression and overrides its current value. |
ClearValue |
Removes local binding state and restores the style/default value. |
| Remove a style after a current-value override | Releases the override owned by that style. |
The shared reference fixture exercises all 110 WPF scenarios, including the formerly separated binding/style cases. Native Dock properties keep their ordinary Avalonia value policy.
LastFocusedDocument remembers the latest document or tool hosted in a document
pane. Activating an ordinary tool preserves that value. The previous and new
nodes notify IsLastFocusedDocument, and the root notifies LastFocusedDocument.
Window deactivation retains this history. The WPF notification order also permits
reentrant callbacks to redirect activation while the outer transition still records
its document history. Inspect the active node separately when redirecting focus
from these callbacks.
Select and reorder pane contents
LayoutDocumentPane and LayoutAnchorablePane expose SelectedContentIndex
and SelectedContent. Setting the index, or setting a child's IsSelected,
updates the selected tab without changing LayoutRoot.ActiveContent. An invalid
index clears selection. Use IsActive or the item's activation command to also
activate it:
pane.SelectedContentIndex = 1;
LayoutContent? selected = pane.SelectedContent;
if (selected is not null)
selected.IsActive = true;
Content IsSelected writes its flag before updating the parent selector. Changing
the pane index clears the old child's flag first; that child can recursively clear
the pane index to -1 before the outer assignment selects its requested child.
Observers therefore see each intermediate state, including an empty selection.
Nested assignments take effect immediately, and the outer assignment continues
after the callback returns. An exception retains the state reached before the
failure; repeating an unchanged content flag does not repair its parent index.
The native presentation receives completed pane selection without tearing down
editors or open menus during the nested clear.
Dock's ShouldSynchronizeDockingWindowSelection policy lets the adapter retain
these flags during native projection while factory events and tracking still run.
When the selected item is removed, rooted document panes select and activate the
remaining child with the latest LastActivationTimeStamp. Equal timestamps retain
child order; this history rule includes disabled children. Detached document panes
and tool panes select the first enabled child, or remain unselected when no child
is enabled. Emptying a rooted document pane clears the root's active content and
document history, including when a tool in another pane was active. These rules
apply to public collections and removal through the native Dock factory.
MoveChild(oldIndex, newIndex) changes the collection before invoking the protected
ChildMoved hook; identical indices are a no-op. Document panes update their stored
selected index in that hook only when it equals oldIndex. Tool panes first scan
their selected child flags during the collection notification, then apply the same
hook rule. As in AvalonDock, moving another child can leave the stored index and
child selection flags referring to different items. Tool panes also notify CanClose, CanHide, and
IsDirectlyHostedInFloatingWindow when their collection changes. CanClose and
CanHide are true for an empty tool pane, as no child disallows the operation.
Extend content state and closing behavior
Derived documents and tools can override OnIsSelectedChanged(bool oldValue, bool newValue) and OnIsActiveChanged(bool oldValue, bool newValue). The native
model holds the state; reentrant callbacks can change it before an outer hook
runs. Activation arguments retain the previous and requested values. The hooks precede the
corresponding PropertyChanged notification; their base implementations raise
IsSelectedChanged and IsActiveChanged. The active hook's base implementation
also updates LastActivationTimeStamp on activation. Omitting the base call
suppresses those effects while retaining the underlying state change.
Ordinary activation hooks run after pane selection and root active content are
synchronized. Explicit nested assignments publish their hooks immediately, before
the outer assignment resumes. Native focus operations still coalesce temporary
native flag changes. Setting IsActive=false retains the root's active-content
reference and selected tab. An override can redirect activation to another content
item; the outer hook still reports its requested value. Throwing from a callback does not roll back
completed model changes; pending callbacks release their references and later
focus operations remain usable. Native floating hosts continue to bind their
title to the focused native content.
New documents and tools start with a null ContentId. When Content changes to
a named control, its name supplies a still-null identifier after content
notifications finish. Explicit identifiers and empty strings are retained. The
same content reference produces no notifications or identifier fallback; a
distinct object that compares equal still produces the content notifications.
Renaming a control does not change an existing identifier.
LayoutItem.ContentId is an independent, initially null container input. Changes
to it forward the value, including null, to the layout content. Model changes do
not overwrite it. Applications can bind the property explicitly in their
container theme when it should track a ViewModel identifier.
LayoutContent.Title and ContentId remain native-backed direct properties.
Their compiled bindings default to one-way. The compatibility metadata makes a
CLR assignment or SetValue replace a local one-way binding, including an equal
assignment. Two-way bindings remain attached and update their source.
SetCurrentValue preserves the binding; ClearValue removes the local binding
and restores the default or styled value. Typed, untyped, indexer, and base-object
property access use the same reflection-free framework value policy.
OnClosing(CancelEventArgs) and OnClosed() provide the corresponding close
overrides. Call their base implementations to raise Closing and Closed.
For example, an application model can veto a close until its workspace command
has saved or discarded changes:
using System.ComponentModel;
using AvalonDock.Layout;
public sealed class WorkspaceDocument : LayoutDocument
{
public bool HasUnsavedChanges { get; set; }
protected override void OnClosing(CancelEventArgs args)
{
args.Cancel |= HasUnsavedChanges;
base.OnClosing(args);
}
}
The manager's DocumentClosing event runs before the document hook; manager
cancellation prevents that hook from running. The content can then cancel in its
override or event. On success, removal and layout cleanup finish before OnClosed,
so Parent and Root are null in the callback. The callback can insert the item
into another layout without subsequent cleanup undoing that insertion. The
originating manager raises DocumentClosed afterward. Public closes, item
commands, native factory operations, and floating-host closes use these hooks.
Observe layout membership and parent changes
LayoutRoot.ElementAdded and ElementRemoved provide LayoutElementEventArgs.
Its Element is the public layout node whose parent transition entered or left
that layout. Attaching or removing a subtree reports its topmost transitioning
node; the events do not enumerate all descendants. Moving directly between panes
in the same root does not change root membership. A move that first detaches a
node and then reattaches it produces removal and addition notifications.
After a leaf has recorded its root, public pane removal raises ElementRemoved before
the pane replaces its selection and activation history. Removing a whole group
clears focus on its descendants before that membership notification.
Derived documents, tools, and other layout types can override
OnParentChanging(ILayoutContainer?, ILayoutContainer?),
OnParentChanged(ILayoutContainer?, ILayoutContainer?), and
OnRootChanged(ILayoutRoot?, ILayoutRoot?). Parent still identifies the old
container during the changing hook and the new container during the changed
hook. The base root hook raises the membership events; call it when overriding
to retain that behavior. RaisePropertyChanging and RaisePropertyChanged are
also virtual. Parent property notifications bracket these hooks, and Updated
notifies the current root after property observers finish. A reentrant observer
can change that destination. Parent transitions notify Parent without an
additional Root property notification.
Updated reports completed parent changes, rather than every collection edit.
Reordering tabs or floating windows and propagating group-tree notifications do
not raise it. Replacing a root panel or floating child reports the incoming
child's attachment once; removing a floating child whose public parent is
retained does not add an event. Native commands and deferred container cleanup
use internal structure notifications for these mutations.
The Root getter walks the current parent chain. An unparented LayoutRoot.Root
is null; GetRoot() also considers the element itself and returns that root.
Membership hooks compare against the root recorded after the element's last
successful OnParentChanged callback. Moving an ancestor leaves the child's
record unchanged. If a parent hook throws after assignment, the new parent
remains while the earlier root record is retained. The record advances before
OnRootChanged, so a failure in that hook leaves the new record in place.
Parent also accepts an application-defined ILayoutContainer. The public
parent/root chain is available without a native Dock owner. A custom
ILayoutRoot reaches the protected root hook; its default implementation casts
to LayoutRoot to publish membership events. An override may replace that
behavior when supporting another root implementation.
The default LayoutAnchorSide.OnParentChanged requires a layout root. A null or
unrooted parent raises NullReferenceException after the parent is assigned,
before membership and changed notifications. The adapter releases native pinned
ownership even if this hook fails or an override suppresses its base call. A
later valid parent assignment restores the projection for a current root slot.
Native factory moves use the same callbacks. Dock.Model.Avalonia.Core.DockableBase
provides OwnerChanging, with DockableOwnerChangingEventArgs from
Dock.Model.Core.Events, so the adapter can notify before the native assignment.
Both public Parent assignments and native owner assignments check thread access
before observer callbacks or ownership projections change. A throwing changing
hook prevents that owner assignment; it does not roll back collection mutations
that a larger docking operation has already performed.
The content hooks also maintain pane selection. A document or tool clears
IsSelected before leaving its previous parent. An item that was already
selected while detached selects its destination pane when inserted. Selection
also synchronizes between panes and their native content flags before a layout
root attaches its factory. Public collection notifications occur after ownership
initialization, so observers can inspect the new parent and selected content.
Groups cache IsVisible and recompute it after parent and child changes.
The protected setter invokes OnIsVisibleChanged, whose base implementation
propagates visibility to ancestors. Call the base hook from an override to
preserve that propagation. Newly created empty, detached groups start visible
until their first computation; wrappers of existing native subtrees initialize
from the native hierarchy. An empty document pane stays visible outside a
LayoutDocumentPaneGroup; inside one, it requires visible content. The document
group itself remains visible. Tool panes and tool groups require a visible child,
while auto-hide sides require a group, including an empty retained return group.
Tool parent changes publish IsVisible, IsVisibleChanged, IsHidden, and
IsAutoHidden before the Parent property notification, even when the Boolean
values remain unchanged. Auto-hide sides resolve their edge before publishing
their new parent. Tool panes determine floating membership from the public
ancestor window; IsDirectlyHostedInFloatingWindow uses that window's visible
pane count, including nested groups. Panes observe their current parent group's
collection and release the old subscription when reparented.
The bridge preserves existing wrappers when native ownership joins separate
trees, without retaining an unrelated layout through its identity map. Floating
documents expose their public floating window instead of the internal native
document pane. During native moves, an existing item container remains available
to native rendering until ownership is reassigned and reconciliation completes,
allowing its content presenter to transfer between hosts. The public
GetLayoutItemFromModel API still returns null for an unattached node.
Traverse layout nodes and edit groups
AvalonDock.Layout.Extensions queries the layout hierarchy through its public
interfaces. Descendents() is deferred and visits children in depth-first order.
FindParent<T>() excludes the starting element; GetRoot() returns a starting
root or walks Parent, without reading ILayoutElement.Root. An unmatched
reference or nullable value type returns null; requesting an unmatched
non-nullable value type retains the upstream null-reference cast error.
GetSide() locates the element within the nearest containing split panel.
Group operations through ILayoutGroup retain their method-specific checks:
ReplaceChildAt ignores an invalid index before inspecting the replacement;
RemoveChild and ReplaceChild require the group's child type, while a missing
compatible child is ignored. Public removals detach ownership and repair pane
selection before group observers run. Automatic root-focus cleanup waits for
that replacement selection, preserving activation history through reentrant
removals and throwing observers. Group and ancestor tree callbacks precede
the ChildrenCount notification; an earlier exception prevents later callbacks.
Native Dock operations retain ownership of their intermediate transitions and
auto-hide projections.
Parent and group callbacks run inside the observable collection's reentrancy
scope. With an additional CollectionChanged subscriber, nested add, remove,
replace, move, and clear operations throw InvalidOperationException before
changing either public or native membership. With only the internal subscriber,
permitted nested edits are visible before the callback returns. This applies to
pane children, hidden tools, and floating windows. A callback may remove or
replace an incoming floating window before its native host is initialized;
Dock cancels that registration. Retrying an insertion whose property callback
threw completes ownership and host presentation.
Set AllowDuplicateContent="False" on a LayoutDocumentPane or
LayoutAnchorablePane to prevent dropping matching content into that pane.
A duplicate has both the same Title and the same ContentId, compared as
case-sensitive strings; two null values match. All incoming descendants are
checked, including unselected content in a dragged pane, group, or floating
window. The destination pane's option controls the decision; source and ancestor
options do not override it. The native overlay hides its into target and the
native drop engine rejects the corresponding operation before changing the
layout. Split targets and direct collection/model edits remain available.
Changing either key or the option takes effect on the next drop validation.
Layout objects retain sealed identity equality: both upstream's WPF
DependencyObject and the adapter's AvaloniaObject prevent application layout
subclasses from overriding Equals or GetHashCode.
Child collections require Dock-backed elements and reject cycles. They retain
repeated public references while projecting each native node once. Removing one
occurrence clears its public parent even when another occurrence remains, as in
AvalonDock. Clear() publishes Reset and retains the former public parent
references; native ownership is detached so the rendered Dock graph remains valid.
Traverse the UI trees
AvalonDock.Controls.Extentions retains the spelling of the upstream helper
class and works with AvaloniaObject in place of WPF DependencyObject.
FindVisualChildren<T> and FindLogicalChildren<T> enumerate matching
descendants in depth-first child order, excluding the starting object.
FindVisualAncestor<T> finds the nearest matching visual ancestor;
FindLogicalAncestor<T> prefers logical parents and falls back to visual
parents at a template boundary. FindLogicalAncestorsAndSelf includes the
starting object before following that same parent chain.
using System.Linq;
using AvalonDock;
using AvalonDock.Controls;
LayoutAnchorableControl? toolView = manager
.FindVisualChildren<LayoutAnchorableControl>().FirstOrDefault();
DockingManager? owner = toolView?.FindLogicalAncestor<DockingManager>();
These methods query the current Avalonia UI trees, including Dock's native
controls. Use the layout model's Descendents() extension to walk layout nodes.
Logical descendant searches do not cross visual-only template boundaries. For
example, find a displayed auto-hide preview through the manager's visual
descendants, then query the preview's logical descendants for its content.
FindVisualTreeRoot walks to the topmost visual, using logical parents to reach
the visual tree from nonvisual objects. In Avalonia, that root can be a hosting
visual above a Window; use FindVisualAncestor<Window> to find the window.
Null inputs produce empty descendant/ancestor-and-self sequences or a null root. The two nearest-ancestor methods reject null inputs. Visual searches reject nonvisual starting objects; logical ancestor fallback also rejects a nonvisual object without a logical parent. Searches are live and enumerations are deferred, so a new search reflects reparenting and layout replacement.
Bind item containers and commands
DockingManager.GetLayoutItemFromModel returns a LayoutDocumentItem or
LayoutAnchorableItem from AvalonDock.Controls. Its LayoutElement is the
layout node and its Model is the application content. Title, identifier,
icon, selection, active state, and capabilities synchronize with the live model.
using AvalonDock.Controls;
LayoutItem? item = manager.GetLayoutItemFromModel(document);
if (item?.FloatCommand is { } floatCommand && floatCommand.CanExecute(null))
floatCommand.Execute(null);
A view whose data context is the returned container can bind its commands using the default XML namespace:
<StackPanel x:DataType="LayoutItem">
<TextBlock Text="{Binding Title}" />
<Button Content="Activate" Command="{Binding ActivateCommand}" />
<Button Content="Float" Command="{Binding FloatCommand}" />
<Button Content="Close" Command="{Binding CloseCommand}" />
</StackPanel>
The default ReactiveUI commands cover closing, floating, activation, docking as
a document, closing document sets, splitting document panes, and moving between
adjacent document panes. Tool containers add hide, auto-hide, and dock commands.
Split-command availability honors CanMove, the number of documents, and the
manager's AllowMixedOrientation setting. Moving to an adjacent document pane
does not require CanMove. Individual command properties can be replaced or
bound to application commands.
As in AvalonDock, ICommand.Execute invokes its action without first calling
CanExecute. Buttons and menus perform that query; application code should do
the same when it wants UI restrictions. A direct call can close, hide, auto-hide,
dock, or split content whose command is unavailable. Content cancellation still
applies. Item commands ignore their caller's parameter. Application errors are
returned synchronously to the caller with their original exception and stack,
including nested execution; they are not queued as unhandled ReactiveUI errors.
The protected LayoutItem.Float() hook is an upstream no-op and is not called
by the default float command.
Closing document sets snapshots the documents, skips contents with CanClose
false, then queries and executes each current CloseCommand, including
application replacements. A null command falls back to the model close action.
The current command owns its availability decision; the default command checks
native policy. If an earlier callback removes a later container, execution fails
at that lookup. Direct split/move commands likewise retain errors for invalid
contexts and destinations. Splitting transfers content into a new pane before
publishing that pane in its group; both operations use the Dock-backed pane
collections and activate the moved content without drag/dock notifications.
LayoutAnchorableItem initializes, applies, and clears hide, auto-hide, and dock
defaults before the common LayoutItem commands. When applying defaults, it
synchronizes tool visibility before the base selection/activation/close state.
The protected hooks remain overridable: omitting the base call suppresses that
phase, and a callback failure stops subsequent assignments. Reinitialization
replaces definitions while retaining commands already assigned to properties;
ClearDefaultCommands clears definitions without clearing those properties.
InitDefaultCommands can be called by a derived class before attachment and does
not evaluate its virtual command predicates. CanExecute queries current state.
SetDefaultBindings still requires an attached model: on an unattached tool it
assigns the three tool defaults before failing at visibility; the base/document
hook assigns its ten defaults before failing at model state.
Clearing defaults removes local Avalonia binding expressions whose current value
matches the default, including a null default before initialization. Application
commands and bindings to them remain intact. Raw Bind(property, observable)
subscriptions retain Avalonia ownership and must be disposed by their caller;
use compiled bindings or observable.ToBinding() for binding-expression cleanup.
No reflection is used to discover or remove bindings.
Containers retain identity during moves, floating, auto-hide, and tool hiding.
Removal detaches a container after the current layout mutation; replacing or
deserializing the manager's layout detaches the old containers immediately.
Resolve containers again after loading. Detachment disables and releases
default commands without disposing application commands. IsVisible hides or
shows tools and closes documents, honoring cancellation; Avalonia does not
provide WPF's separate Hidden and Collapsed states.
View is the compiled content presenter used by the native Dock controls. It
moves with its content between docked and floating hosts. Native document close
buttons, tool title-bar close/pin buttons, context menus, tab middle-clicks, and
tab double-clicks use the current container commands in docked and floating
hosts. Menus on auto-hide strips use the same tool container. Command parameters
are null, so parameterless ReactiveCommand properties work directly.
AvalonDock.Controls.LayoutDocumentControl and LayoutAnchorableControl are
the public content controls inside those native panes. Their Model is the
layout node, and their read-only LayoutItem is the manager's existing container.
The default compiled control themes display that container's same View.
Documents and tools hosted in a document pane use LayoutDocumentControl;
ordinary tool panes and auto-hide previews use LayoutAnchorableControl.
Dock continues to own the tab strip, pane chrome, movement, and floating hosts.
Both controls can be used without a namespace prefix in compiled templates:
<DataTemplate x:Key="DocumentContent" x:DataType="LayoutContent">
<LayoutDocumentControl Model="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="ToolContent" x:DataType="LayoutAnchorable">
<LayoutAnchorableControl Model="{Binding}" />
</DataTemplate>
The model must belong to a manager to resolve its LayoutItem. The same view
can have only one visual parent; presenting it elsewhere transfers it from its
previous host. Native presentation reuses controls when switching tabs, so a
control's model can change. Keep the model or item identity when tracking a
document, rather than retaining a particular generated control. Removed native
controls clear their model and container when the item detaches; an explicitly
assigned Model remains available after its container detaches.
The controls follow the model's IsEnabled. Disabling active content selects
the first enabled sibling, or clears pane selection if none remains. Document
controls are focusable by default; tool controls receive focus through their
editors. Keyboard focus activates the model, including an outer content model
when focus enters a nested manager. Focus returning from a floating-window
control retains the upstream activation exception. A native presenter's delayed
focus restoration cannot override a newer tab selection.
The base content-model callback removes one enablement subscription from its
supplied old model, then registers the current model; repeated base calls add
separate registrations. Tool controls register managed models, while native
presentations also support ownership transitions. Existing registrations read
the live Model even if a later override suppresses its base call. Disabling
active content clears selection before scanning the live children, so callbacks
can affect the next enabled item. Document controls apply this policy only to
document panes; tool controls apply it only to tool panes. Registrations use weak
references so retained old models do not keep discarded controls alive.
For standalone controls, assign a document model only after its layout has a
manager: the base hook subscribes, then raises NullReferenceException if
Root.Manager is unavailable. Tool controls accept an unmanaged model but leave
their item null and do not subscribe until another base model callback runs with
a manager. Assigning or clearing a model preserves the control's current
IsEnabled; later model notifications update it. Visual attachment and ownership
changes do not refresh the resolved item. Native Dock presenters separately
initialize enablement, follow ownership changes, and release removed content.
Document pointer-preview hooks activate only when the source is a visual whose
nearest parent DockingManager owns the model. Standalone presentations, foreign
managers, nonvisual sources, and unrooted models do not pass that ownership check.
The preview hooks still run for handled pointer events, as upstream. The focus
hooks have a separate contract: an explicit call from a derived control activates
its model even when the control is disabled; Avalonia controls normal focus
delivery.
Derived controls can override OnModelChanged(AvaloniaPropertyChangedEventArgs)
and use protected SetLayoutItem(LayoutItem?). Call the base model hook to
preserve subscriptions, enablement, and container lookup. Base input hooks read
the current public Model, including a replacement or null model assigned while
a derived OnModelChanged override omits its base call. Document input hooks
are OnPreviewGotKeyboardFocus(FocusChangedEventArgs) and
OnPreviewMouseLeftButtonDown / OnPreviewMouseRightButtonDown
with PointerPressedEventArgs; tools expose
OnGotKeyboardFocus(FocusChangedEventArgs). These use Avalonia event arguments.
The pointer hooks run on the tunnel route; the focus hooks run when Avalonia's
bubbling GotFocus reaches the control, so WPF preview-focus routing is not
reproduced exactly.
AvalonDock.Controls.AnchorablePaneTitle presents the selected tool inside
Dock's native title bar. Its Model is a LayoutAnchorable, and its read-only
LayoutItem is the same container used by the tool's content and commands.
The default theme applies the manager's AnchorableTitleTemplate or selector.
It works in docked panes, floating panes, and auto-hide previews. A custom host
can also use the public control directly:
<DataTemplate x:Key="ToolTitleControl" x:DataType="LayoutAnchorable">
<AnchorablePaneTitle Model="{Binding}" />
</DataTemplate>
Titles are nonfocusable by default. A title release activates its tool. A press
followed by leaving a custom title inside a LayoutAnchorablePaneControl floats
that visual pane through Dock's factory. Outside a pane control, it floats only
the title's Model, even when that model belongs to a pane with other tools.
An auto-hidden tool also floats individually. A tool already in a single-pane
floating window starts that existing window's move. Native tool chrome retains
its own grip handling, so the title does not start a duplicate drag. Interactive
title children retain their handled input. Hosting a title under another manager
does not replace its explicit model: release activates that model, and a drag
outside a tool pane uses that model's owning manager.
Custom title dragging uses HostWindow.TryBeginMoveDrag to preserve native
WindowMoveDragBegin cancellation and drag completion. Canceling the window
drag leaves any preceding pane float in place. Headless tests cover this
lifecycle; platform window movement still needs desktop integration validation.
Derived titles can override OnModelChanged(AvaloniaPropertyChangedEventArgs)
and use SetLayoutItem(LayoutItem?). The protected OnMouseLeftButtonDown,
OnMouseLeftButtonUp, OnMouseMove, and OnMouseLeave hooks receive Avalonia
PointerPressedEventArgs, PointerReleasedEventArgs, and PointerEventArgs.
Call their base implementations to keep the default behavior. Titles share the
content controls' weak model/container subscriptions, recycling, and generated
control cleanup; tracking a tool should use its model or container identity.
Clearing an explicitly assigned title model retains its last LayoutItem,
including across visual attachment. Generated native titles clear their model
and item together when the owning content is released.
Tool title-bar close buttons use CloseCommand when CanClose is true and
HideCommand otherwise. A disabled command cannot fall through to the native
factory's default action. Floating-tool close commands run before the host's
window-close action, preserving application overrides and close/hide
cancellation. Native window decoration close requests still use the window
lifecycle and its cancellation events. Calling layout methods such as Close
or Float directly invokes model operations, independently of UI commands.
As in upstream AvalonDock, capability flags control default UI command availability; some explicit model methods deliberately have different rules:
| Explicit model call | Contract |
|---|---|
content.Close() |
Closes even when CanClose is false. Document-manager, content, and native closing cancellation still apply. |
tool.ToggleAutoHide() |
Pins or restores a tool in an anchorable pane or auto-hide group even when CanAutoHide is false, including a floating tool. Hidden tools and tools in document panes stay in place. |
tool.DockAsDocument() |
Moves to a document pane even when CanDockAsTabbedDocument is false, then selects and activates the tool. |
content.Float() |
Reuses a previous floating pane regardless of CanFloat; creating a new window still requires the native float capability. Both paths select and activate the content. Floating an already floating item can create another window while keeping its original docked return pane. |
content.Dock() |
Restores its previous pane and selects/activates the item. Without a previous pane, it calls the protected InternalDock() without adding an activation step. The completion event precedes clearing the floating flag and collecting empty containers. |
For example, an application can disable a document's close button while keeping its own programmatic close workflow:
document.CanClose = false;
var item = manager.GetLayoutItemFromModel(document);
// item.CloseCommand.CanExecute(null) is false.
document.Close(); // Still raises cancelable closing notifications.
Container metadata callbacks preserve a null title and forward the supplied changed value for title, identifier, selection, activation, capabilities, and text description. The icon callback reads the current icon, as upstream does.
Setting a document container's IsVisible to false updates the document flag
and its pane's computed visibility before requesting closure. Cancellation or
an exception retains that requested visibility and the document; set the
container visible again before retrying. Setting a tool container invisible
maps to WPF's hidden state: it calls Hide(false), bypassing CanHide and the
cancelable Hiding event, while retaining Hidden and normal restoration.
Use the hide command or Hide() when cancellation is required.
These explicit methods do not temporarily enable capabilities. Default item
command queries check their flags and native Dock policies; close-command queries
also honor CanCloseLastDockable. Direct Execute does not repeat those queries.
Replacing a root/pane policy or a content
override refreshes command availability. Native manager.DockFactory methods
retain their own capability checks. The adapter reuses the factory's protected
close and pin operations to preserve native lifecycle events, ItemsSource
removal, pinned ownership, and cleanup.
DockAsDocumentCommand returns a floating document to its previous document
pane. Without a previous document pane, docking uses the last focused document's
pane. If there is no last focused document, it searches the layout's document
panes, including floating panes. It creates a pane if the chosen path has no
destination. DockAsDocument() raises Docked after insertion and before its
explicit selection and activation.
Direct Dock() and creation of a new floating window require a rooted model
with a manager; missing context raises NullReferenceException before movement.
DockAsDocument() rejects a rootless model with InvalidOperationException, but
already being in a rooted document pane is a no-op even without a manager.
Restoring an existing floating pane does not require a manager. Return indices
are clamped only at the upper end; a negative index fails before insertion.
Collection observers see the requested insertion index in the add notification.
Previous panes can belong to another layout; the original manager receives
PreviewDock, and the manager of the resulting layout receives Docked.
Callbacks can change or remove that manager, and failures stop the operation
at the callback phase without undoing completed collection changes.
CollectGarbage() validates return containers through their parent's root.
Disconnected or foreign references are cleared without rewriting their saved
index or identifier. A visible, docked content item loses its reference to an
empty return pane, including a floating pane; its index becomes -1 and its
saved identifier remains. Hidden and floating contents retain their return
panes. Empty referenced panels are not treated as panes.
Collection removes empty panes in tree order and retains the last document
pane. After each pane removal it prunes one empty tool group, panel, floating
window, and auto-hide group, in that order. Without a pane removal, those
containers remain; an unshown empty document window can therefore remain in
FloatingWindows. Shown document hosts also have their deferred close lifecycle.
An emptied root panel resets to the default document layout. Finally, nested
tool groups collapse before document groups, using the live children after
each callback. New groups created by callbacks are included in the current phase.
Docking the last visible tool out of a floating pane collects that empty pane
and its return reference. A later Float() creates a new window. A populated
floating return pane remains reusable, including after XML restoration. Pinned
and hidden XML return owners are resolved into native Dock state before content
callbacks and collection, preserving their floating destinations.
Use public document and tool tabs
AvalonDock.Controls.LayoutDocumentTabItem and LayoutAnchorableTabItem are
public headers inside Dock's native document and tool tab containers. Both
accept a LayoutContent in Model and expose the manager's existing, read-only
LayoutItem. A tool docked as a document uses LayoutDocumentTabItem and keeps
its LayoutAnchorableItem. Auto-hide strips keep their separate native pin
presentation. Dock's native theme hides the tab strip when a tool pane contains
only one tool; the tool title remains visible.
The default themes use DocumentHeaderTemplate or AnchorableHeaderTemplate
and their selectors; their data remains the public layout node. Usually,
customize those manager templates. For an application-provided tab row, bind the
public controls to the live pane children. This resource can be declared in a
view using Avalonia's default XML namespace:
<DataTemplate x:Key="DocumentTabs" x:DataType="LayoutDocumentPane">
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="LayoutContent">
<LayoutDocumentTabItem Model="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
For tools, use LayoutAnchorablePane for the outer data type and
LayoutAnchorableTabItem for each header. Both controls also compile with the
historical ad namespace alias. Include AvalonDockTheme in the application or
host window so the controls receive their compiled themes.
Custom document tabs activate on left press; custom tool tabs activate on left
release. A document's middle-click invokes its current item CloseCommand when
the command can execute. CanClose controls the default command; replacing the
command can permit a middle-click action while CanClose is false. Explicit
native root/pane policies and per-item overrides still apply in their normal
precedence. The middle-click hook uses LayoutItem even when Model is unset
and preserves the event's handled state. Children that handle input keep it.
Dragging across a custom row calls the live pane's MoveChild; a bound
row follows the new collection order. Leaving the row floats that content and
starts its native window drag. Factory cancellation stops the window drag but
does not undo the preceding float. Native tab containers retain Dock's existing
selection, drag preview/drop, context-menu, and command input.
LayoutDocument.CanMove, model enablement, and native drag capabilities gate
tab dragging. The pane's and its immediate group's CanRepositionItems gate
reordering while still allowing an outside drag. The compatibility factory
implements the optional Dock.Model.Core.IDockItemDragPolicy so native tab input
honors these restrictions too. Factories without this policy retain Dock's
existing behavior; direct factory operations are unaffected. Restrictions
changed during a native gesture are checked before moving the item.
Derived controls can override OnModelChanged(AvaloniaPropertyChangedEventArgs)
and call protected SetLayoutItem(LayoutItem?). Their protected mouse-named
hooks use Avalonia arguments: OnMouseLeftButtonDown(PointerPressedEventArgs),
OnMouseLeftButtonUp(PointerReleasedEventArgs), and
OnMouseMove, OnMouseLeave, and OnMouseEnter with PointerEventArgs.
LayoutDocumentTabItem additionally exposes
OnMouseDown(PointerPressedEventArgs) for middle-click handling. Call the base
hook to preserve its normal behavior. These are framework-control extension
points; application actions belong in bound ViewModel commands or services.
The document press and tool/title release activation hooks act on their model
even when called explicitly with a handled event, a foreign or focusable source,
or a disabled control. Avalonia controls normal routed input delivery; native
gesture policies independently determine whether a drag may start.
Weak model/container subscriptions follow manager transfers. XML replacement clears generated tabs' old models and resolves the replacement nodes; custom tabs retain an explicitly assigned model until its binding is updated. Dock can recycle generated headers, so retain model or item identity instead of assuming one control per document. Changing or detaching a custom tab cancels its pending drag. Headless tests cover collection updates, capabilities, cancellation, manager transfer, XML replacement, and retained-control lifetime. Native desktop move behavior still needs platform integration validation.
Style document and tool panes
LayoutDocumentPaneControl and LayoutAnchorablePaneControl derive from
LayoutCachePaneControl, an Avalonia TabControl. Their read-only Model
implements ILayoutElement and identifies the existing public pane;
ItemsSource is that pane's live Children collection. The default templates
host Dock's document/tool controls, retaining native tab input and the existing
LayoutItem.View. Floating document and tool panes use the same control themes.
Set DockingManager.DocumentPaneControlStyle and
DockingManager.AnchorablePaneControlStyle to Avalonia ControlTheme resources.
Use BasedOn to retain the default native presentation. Both the unprefixed
types below and the historical ad: XML namespace compile:
<ControlTheme x:Key="DocumentPaneAppearance"
TargetType="LayoutDocumentPaneControl"
BasedOn="{StaticResource {x:Type LayoutDocumentPaneControl}}"
x:DataType="LayoutDocumentPaneControl">
<Setter Property="MinHeight" Value="100" />
<Setter Property="Tag" Value="{Binding $self.Model}" />
</ControlTheme>
Apply it with DocumentPaneControlStyle="{StaticResource DocumentPaneAppearance}"
on the manager. For tool panes, use LayoutAnchorablePaneControl as the target
and set AnchorablePaneControlStyle. Replacing either property updates existing
panes, including floating panes; clearing it restores their default theme.
Panes follow their owning manager's flow direction and theme after transfer.
Set LayoutDocumentPane.ShowHeader="False" to hide that pane's tabs, document
list menu, and separator. The selected content fills the available space and
keeps its view, selection, activation, and keyboard focus. For example, with the
ordinary Avalonia default XML namespace:
<LayoutDocumentPane ShowHeader="False">
<LayoutDocument Title="Preview" ContentId="preview">
<TextBox Text="Preview content" />
</LayoutDocument>
</LayoutDocumentPane>
ShowHeader defaults to true and updates the current pane immediately. It is
saved in layout XML, works after restoring or reattaching the manager, and remains
independent for each docked or floating pane. Floating a document creates a new
pane with the default policy; docking back restores the original pane's policy.
The adapter also respects Dock's dynamic DockDocumentControlTabStripVisible
resource: either setting can hide the header. Custom pane templates can bind to
Model.ShowHeader to apply the same policy to their own header controls.
Changing the model's SelectedContentIndex updates the control without
activating another item. Setting a document control's SelectedItem or
SelectedIndex explicitly selects and activates that document; the tool control
selects without activation. Both support an empty selection. Tool-pane focus
activates its selected tool. Pane left/right presses require an unhandled event
and a source visual whose nearest parent manager owns the pane; a standalone
pane or pointer input inside a foreign or nested manager does not activate it.
Tool-pane focus hooks operate on selection without that pointer ownership test.
Collection-driven selection is applied
after item observers finish, and tab moves restore selection through the passive
model API. Ordinary Dock strips keep their existing selection defaults.
The upstream document-pane constructor is internal, so obtain its generated
control from the visual tree or target its type in a theme. The tool-pane
constructor is public: new LayoutAnchorablePaneControl(existingPane). Its
protected OnGotKeyboardFocus, OnMouseLeftButtonDown, and
OnMouseRightButtonDown hooks use Avalonia focus/pointer arguments. Managers
expose OnDocumentPaneControlStyleChanged and
OnAnchorablePaneControlStyleChanged with AvaloniaPropertyChangedEventArgs.
LayoutCachePaneControl.OnSelectionChanged(SelectionChangedEventArgs) marks
empty-selection notifications handled before they bubble.
The document pane's protected IEnumerator LogicalChildren hook returns an
empty enumeration, matching upstream. Grouped and mixed native windows use the
additional LayoutGroupFloatingWindow adapter. Pane presentation continues to
use Dock's content caching.
DockingManager.LogicalChildrenPublic returns the manager's virtual logical-child
enumerator. Its default membership uses weak references to layout/side controls,
the auto-hide control, initial application controls, and lazily created
LayoutItem.View presenters. Layout item containers are excluded. Replacing a
content value does not replace the originally registered logical control; layout
replacement clears the old registrations. An expired weak reference remains a
null entry in the public enumeration. Framework traversal uses the same virtual
hook and filters non-logical entries. Calling the base presentation-property
callbacks performs membership changes; suppressing the base suppresses them.
Inspect and style split containers
LayoutPanelControl, LayoutDocumentPaneGroupControl, and
LayoutAnchorablePaneGroupControl derive from the public Avalonia
LayoutGridControl<T>. Each exposes the existing public layout node through
Model and reads its current Orientation. Their constructors are internal,
as upstream; use generated controls or target their types in styles. Both XML
namespace forms work, including these unprefixed compiled bindings:
<Style Selector="LayoutDocumentPaneGroupControl"
x:DataType="LayoutDocumentPaneGroupControl">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Tag" Value="{Binding $self.Model}" />
</Style>
After the manager loads, manager.LayoutRootPanel is the actual
LayoutPanelControl displayed for manager.Layout.RootPanel. For example:
using AvalonDock.Controls;
using AvalonDock.Layout;
LayoutPanelControl? control = manager.LayoutRootPanel;
if (control?.Model is LayoutPanel panel)
{
panel.Orientation = Avalonia.Layout.Orientation.Vertical;
// control.Orientation now reads Vertical from the same model.
}
Compatibility grid styles disable layout rounding, matching WPF's default, so fixed pixel requests and splitter limits remain exact when panes leave unused space. Model minimums belong to the row/column definitions, allowing public pane themes to set independent control minimums. Empty panes retain zero-length slots; one splitter remains between visible panes separated by an empty pane.
The property starts as null. Replacing the root panel or deserializing a layout
replaces its control. Setting the property to null removes the displayed root;
restoring the previous control reuses the existing content views. Manager
subclasses can override OnLayoutRootPanelChanged with
AvaloniaPropertyChangedEventArgs. Detaching the manager releases the old
presenter, and reattachment displays the current root control. Split-container
controls set their local flow direction to left-to-right, matching upstream.
Document and tool pane groups normalize non-star child lengths to 1* on their
split axis when initially displayed or rebuilt after direct-child, visibility,
or orientation changes. Existing star weights and cross-axis lengths remain
unchanged; hidden children also participate. A pixel length assigned after that
layout is honored until the next such rebuild. LayoutPanelControl applies no
star conversion. Mixed pixel/star layouts use actual Grid definitions and the
native splitter input described below.
The inherited Children collection contains public panes, nested public grids,
and LayoutGridResizerControl instances directly. The active axis exposes one
real definition per child. Hidden panes keep a zero-length definition, and only
gaps between visible panes consume splitter space. Reordering reuses pane controls.
Changes to model lengths, minimums, visibility, orientation and manager splitter
sizes update the definitions. Caller edits to those definitions affect layout
until model metadata updates them.
DockableControlTracker connects each grid to the native factory's bounds and
pointer tracking; IProportionalSplitterHost reuses native splitter input,
previews and cancellation. Pane content remains in compiled Dock templates.
After arrangement, pane sizes update native proportions without rewriting public
length units. Avalonia Grid preserves fixed lengths and measures Auto from
content and chrome. These controls also render floating tool groups.
Lay out tab headers and handle overflow
DocumentPaneTabPanel and AnchorablePaneTabPanel are public Avalonia Panel
controls. The compatibility theme installs them in Dock's existing horizontal
document and tool tab strips, including floating windows. Both start with
FlowDirection="LeftToRight" and also compile through the historical XML URI.
Vertical native document strips keep their existing layout.
Document headers measure at their natural width. The panel displays the leading
headers that fit; once one overflows, the remaining headers are suppressed. A
newly selected hidden document moves to index zero through the live pane's
MoveChild, and its selected index follows. This changes the public collection
and its native Dock collection together; it is distinct from activating content.
The native document-list menu can activate an overflowed document and bring its
tab into view. A first header wider than the entire available area remains
suppressed until sufficient space is available.
Avalonia has no separate WPF Visibility.Hidden value. Overflowed headers retain
their measurement and application IsVisible value while temporary property
layers set opacity to zero and disable input/focus. Resizing restores the
underlying Opacity, IsHitTestVisible, and IsEnabled values and bindings.
Removing a header or detaching the panel releases those layers and subscriptions,
including removal during a property notification. Native drag reordering ignores
noninteractive targets, including headers suppressed by overflow.
Tool headers measure naturally when space permits. Under constraint, they are remeasured with an equal width cap. Arrangement uses their desired widths when there is spare room, or equal shares when the measured total fills or exceeds the available width. Collapsed children do not consume a share. Native tool containers clip custom header content to their allocated tab slots. Tool strips still hide when they contain only one tool.
Custom rows can use either public panel as an ItemsControl.ItemsPanel:
<ItemsPanelTemplate x:Key="DocumentTabLayout">
<DocumentPaneTabPanel />
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="ToolTabLayout">
<AnchorablePaneTabPanel />
</ItemsPanelTemplate>
Bind the row's ItemsSource to the live pane's Children, as in the public-tab
example above. Model-aware overflow accepts public tab controls, TabItem
containers with a LayoutContent in Content, and generated containers whose
data context is a public content node or its native Dock model. Keep the collection
binding so programmatic selection can update the visual order.
Both panels expose protected MeasureOverride(Size) and ArrangeOverride(Size)
for derived controls. DocumentPaneTabPanel.OnMouseLeave(PointerEventArgs) is the
Avalonia counterpart of the upstream mouse-exit hook; its base implementation
preserves normal pointer routing. Ordinary application layouts need only the
compiled panel templates.
Customize auto-hide strips
An explicit tool.ToggleAutoHide() creates one LayoutAnchorGroup for that
tool. Tools pinned from the same pane therefore have separate groups; hiding
and showing a tool retains its group and strip position. Native factory pinning
can still project multiple tools from a pane into one group.
Floating tools pin into the manager's main strip and retain their floating
return pane through preview open/close cycles and XML restoration. As upstream,
IsFloating remains true during this state; use IsAutoHidden or the current
layout ancestors to determine its current location. Float and auto-hide commands
check those ancestors. Restoring the tool uses the existing floating pane.
When a group has no return pane, restoring a tool creates a new pane on its
edge. A panel with the matching orientation accepts it directly and applies
AutoHideMinWidth or AutoHideMinHeight; a different orientation is wrapped
in a new root panel, whose pane uses the default minimums. The operation selects
the tool without explicitly activating it. Replacing the root panel clears
active content through the normal root-removal contract. Showing an existing
floating host can restore that native root's focused content.
Restoring or closing an auto-hidden tool redirects hidden tools that remember
its group to the return pane, even if other tools remain in the group. Closing
performs that cleanup only after cancellation succeeds. Hiding a tool also
removes its native pin and preview when CanAutoHide is false.
The four side properties on DockingManager expose the controls displayed by
Dock's native pinned strips: LeftSidePanel, TopSidePanel, RightSidePanel,
and BottomSidePanel. Each is a LayoutAnchorSideControl whose Model is the
corresponding LayoutAnchorSide and whose observable Children contains
LayoutAnchorGroupControl instances. Each group's Children contains
LayoutAnchorControl instances over its existing LayoutAnchorable models.
The side controls expose IsLeftSide, IsTopSide, IsRightSide, and
IsBottomSide; each anchor exposes Side. All three control types implement
ILayoutControl and live in AvalonDock.Controls.
Use the manager's AnchorSideTemplate, AnchorGroupTemplate, and
AnchorTemplate properties to customize this hierarchy. These are Avalonia
IControlTemplate properties. Model-bearing constructors remain internal, as
in AvalonDock; applications style or template the manager-created controls.
This example uses the ordinary Avalonia XML namespace without an AvalonDock
prefix:
<DockingManager.AnchorTemplate>
<ControlTemplate TargetType="LayoutAnchorControl" x:DataType="LayoutAnchorControl">
<Border Padding="8,5" Background="Transparent">
<TextBlock Text="{Binding ((LayoutAnchorable)Model).Title, RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
</ControlTemplate>
</DockingManager.AnchorTemplate>
The historical http://schemas.xceed.com/wpf/xaml/avalondock namespace resolves
the same types. Bind a custom side/group template's ItemsControl.ItemsSource
to Children on its templated parent. The default group uses Dock's
ToolPinnedControl, and anchors extend its native pinned item control so the
default presentation retains pinned reordering and docking drag input. Template
bindings to public models should use an explicit x:DataType and cast Model
to the relevant layout type, as above.
An unhandled pointer press opens the native preview and activates the tool.
Hovering for 400 milliseconds opens a preview without replacing the active
document; leaving before the delay or detaching cancels the pending preview.
Setting an attached auto-hidden tool's IsSelected also opens a passive preview
and resets the selection flag to false. Setting IsActive opens and activates
the preview. Disabled anchors cannot open it, and interactive children such as
buttons can handle their own pointer presses. The preview displays the existing
LayoutAnchorableControl and LayoutItem.View.
Changing the three templates updates existing controls. Setting a side-panel
property to null removes that side's presentation; restoring the control reuses
it. A new side model or XML layout creates the corresponding new controls.
Derived managers can override OnLeftSidePanelChanged, OnTopSidePanelChanged,
OnRightSidePanelChanged, and OnBottomSidePanelChanged with
AvaloniaPropertyChangedEventArgs.
The manager creates its readonly AutoHideWindow when loaded. This
LayoutAutoHideWindowControl wraps Dock's existing preview; Model identifies
the current LayoutAnchorable and becomes null when the preview closes. The
native preview still owns content placement and pinned bounds. The public
control hosts the existing LayoutAnchorableControl and LayoutItem.View.
The native auto-hide host presents LayoutGridResizerControl. Manager
GridSplitterWidth and GridSplitterHeight size its horizontal and vertical
resize axes, including runtime changes; NaN uses six. The tool's
AutoHideMinWidth and AutoHideMinHeight default to 100. An unset size uses the
minimum when the preview opens. Size assignments clamp to the current minimum and raise
changing/changed notifications. Negative minima throw ArgumentException;
changing a minimum leaves the stored requested size and an already open preview intact.
Dragging displays the public resizer's brush and opacity and commits
AutoHideWidth or AutoHideHeight to Dock's existing pinned bounds on release.
Escape, capture loss, disabling resizing, switching tools, replacing the layout,
and detachment discard an unfinished drag. All four edges, right-to-left
content, and native inline previews are supported. The ghost follows upstream
physical limits relative to the auto-hide area: horizontal coordinates run from
25 to the area's width, and vertical coordinates run from 25 to its height minus
AutoHideMinHeight. These bounds apply before the model coerces the committed
size to its minimum. The adapter's scoped style disables native remaining-space
reservation and automatic initialization of unset pinned bounds; ordinary Dock
controls retain their defaults. Dock's additional inline mode uses resize limits
relative to the current content size. The resizer sits beside the public preview
content in PinnedDockControl, so target it directly or through the manager:
<Style Selector="LayoutGridResizerControl" x:DataType="LayoutGridResizerControl">
<Setter Property="BackgroundWhileDragging" Value="Teal" />
<Setter Property="OpacityWhileDragging" Value="0.4" />
</Style>
AutoHideWindowClosingTimer specifies the idle closing delay in milliseconds
(1500 by default; negative values coerce to 1500). The preview checks for idle
every 50 milliseconds, then waits for the configured delay. An active tool,
pointer over the preview or its anchor, or a native splitter drag keeps it open.
Interaction at the close deadline returns to idle monitoring. Changing the
property updates an already pending deadline. Dock's KeepPinnedDockableVisible
also keeps the native preview open. Closing, unpinning, hiding, floating, layout
replacement, and manager detachment clear the public preview model.
OnAutoHideWindowClosingTimerChanged applies its argument to the initialized
timer. Suppressing or throwing before the base call preserves the applied
interval, including for later previews; redirecting or replaying the callback
uses the order and values of the base calls. Before the preview infrastructure
is initialized, setup reads the current property value. Assigning a different
property value without a base callback does not retime an initialized timer.
Style its Background and AnchorableStyle through ordinary Avalonia XAML;
AnchorableStyle is a ControlTheme for the preview's content control. Both XML
namespaces resolve this API. For example, in a window with AvalonDockTheme:
<Window.Resources>
<ControlTheme x:Key="PreviewContent" TargetType="LayoutAnchorableControl"
BasedOn="{StaticResource {x:Type LayoutAnchorableControl}}">
<Setter Property="Margin" Value="8" />
</ControlTheme>
</Window.Resources>
<Window.Styles>
<Style Selector="LayoutAutoHideWindowControl" x:DataType="LayoutAutoHideWindowControl">
<Setter Property="Background" Value="Transparent" />
<Setter Property="AnchorableStyle" Value="{StaticResource PreviewContent}" />
</Style>
</Window.Styles>
Derived managers can use SetAutoHideWindow and override
OnAutoHideWindowChanged or OnAutoHideWindowClosingTimerChanged, with
AvaloniaPropertyChangedEventArgs. To use a separate native window for Overlay
previews, enable Dock.Settings.DockSettings.UsePinnedDockWindow. The owned
PinnedDockWindow receives the manager's current theme resources and presents
the same AutoHideWindow, content style, and cached editor. Manager context
menus, close commands, and Ctrl+Tab navigation work inside it. Changing a tool's
PinnedDockDisplayModeOverride to Inline returns its editor to the manager;
changing back creates a new native preview. The resizer stays beside the pinned
area in the manager. Windows 11 ARM64 desktop checks cover native pointer
resizing and editor focus on all four edges for the earlier native preview
implementation. The current BuildWindowCore and DestroyWindowCore hooks
create and dispose an embeddable native root around the same presenter and
editor on Windows and macOS. Avalonia owns native parenting, position, clipping,
and visibility. Headless and X11 keep inline presentation because those
backends do not support embedded roots. Current desktop checks remain pending.
Use converters in compiled bindings
Converters live in AvalonDock.Converters and resolve through both XML namespaces.
For a template whose data is a public LayoutAnchorable, the command converters
use the live manager-owned container:
<DataTemplate x:DataType="LayoutAnchorable">
<StackPanel>
<StackPanel.Resources>
<ActivateCommandLayoutItemFromLayoutModelConverter x:Key="ActivateFromModel" />
<HideCommandLayoutItemFromLayoutModelConverter x:Key="HideFromModel" />
<AutoHideCommandLayoutItemFromLayoutModelConverter x:Key="AutoHideFromModel" />
</StackPanel.Resources>
<Button Content="Activate" Command="{Binding ., Converter={StaticResource ActivateFromModel}}" />
<Button Content="Hide" Command="{Binding ., Converter={StaticResource HideFromModel}}" />
<Button Content="Auto Hide" Command="{Binding ., Converter={StaticResource AutoHideFromModel}}" />
</StackPanel>
</DataTemplate>
Activation supports documents and tools. Hide and auto-hide return the tool
container's command, or BindingOperations.DoNothing for a document container.
Inputs without a manager return null. Conversion resolves the current container
and its current command, including application overrides; replacing a command
alone does not cause the converter's source binding to reevaluate. Binding
directly to the container's command property observes such replacements.
| Converter | Avalonia result |
|---|---|
LayoutItemFromLayoutModelConverter |
Current LayoutItem container. |
BoolToVisibilityConverter |
Boolean for IsVisible; null defaults to false. |
InverseBoolToVisibilityConverter |
Inverted Boolean for IsVisible; requires Boolean input. |
AnchorableContextMenuHideVisibilityConverter |
MultiBinding of existing IsVisible and CanClose; returns false when closing is available, otherwise preserves the first input. |
AnchorSideToAngleConverter |
90d for left/right; BindingOperations.DoNothing for other sides. |
AnchorSideToOrientationConverter |
Vertical for left/right; horizontal for other sides. |
NullToDoNothingConverter |
BindingOperations.DoNothing for null; otherwise the original value. |
WPF's Visibility values are adapted to Avalonia's Boolean IsVisible.
Both Hidden and Collapsed correspond to false. The two Boolean converters accept
a Boolean parameter as their non-visible result; they also support conversion
back to Boolean. Other converters in this group throw NotImplementedException
for reverse conversion. The hide-menu converter retains its public array-based
Convert/ConvertBack methods and implements Avalonia's list-based
IMultiValueConverter for XAML bindings.
AnchorableContextMenuAutoHideHeaderConverter returns the public Auto Hide or
Restore resource string from a Boolean auto-hide state. The command labels and
hints are exposed by AvalonDock.Properties.Resources, including its
ResourceManager and optional Culture override. Setting Culture affects
subsequent lookups without changing CultureInfo.CurrentUICulture or refreshing
already-created static XAML values or resource dictionaries. All 21 resource keys
are included in English and in cs-CZ, de, es, fr, hu, it, ja-JP,
nl-BE, pt-BR, ro, ru, sv, and zh-Hans satellite assemblies. The
translations were written for Dock.
// Configure this before constructing the theme and controls.
AvalonDock.Properties.Resources.Culture =
System.Globalization.CultureInfo.GetCultureInfo("fr-CA");
Lookup follows .NET's culture-parent chain: fr-CA resolves to fr, and a
missing locale falls back to English. A regional resource such as pt-BR does
not replace a sibling culture such as pt-PT. Null restores lookup through
CultureInfo.CurrentUICulture. ResourceManager.GetString(key, culture) uses
its explicit culture independently of the typed properties' override.
Compatibility menus use these labels while retaining their dynamic resource keys for application overrides. Floating document/tool maximize buttons update their localized tooltip and accessible name when the window changes between normal and maximized state. Existing controls must be recreated to refresh static XAML labels after changing the culture.
UriSourceToBitmapImageConverter creates an Avalonia Image from file, compiled
avares, HTTP, and HTTPS URIs. Its optional converter parameter supplies the
absolute base URI for a relative path. Null returns BindingOperations.DoNothing;
reverse conversion is unsupported. Both converters support the default and
historical XML namespaces:
<Window.Resources>
<UriSourceToBitmapImageConverter x:Key="UriImage" />
<AnchorableContextMenuAutoHideHeaderConverter x:Key="AutoHideHeader" />
</Window.Resources>
HTTP/HTTPS conversion returns the image immediately; its Source is populated
on the UI thread after downloading and decoding. Detaching cancels a pending
request, and reattaching retries it. A completed bitmap is retained across view
reattachment. Replacing Image.Source yourself cancels the pending load, so a
late response cannot overwrite your replacement. Pending downloads hold only a
weak reference to their target image.
The default client handles redirects and normal TLS certificate verification.
Network, HTTP status, timeout, and decode failures leave the image empty and log
a warning in Avalonia's AvalonDock log area. Request timeout covers the response
body as well as the headers. Applications can supply a configured HttpClient
for authentication, proxy settings, or a custom timeout:
using System;
using System.Net.Http;
using AvalonDock.Converters;
using HttpClient client = new() { Timeout = TimeSpan.FromSeconds(15) };
using UriSourceToBitmapImageConverter converter = new(client);
// Keep both objects alive while the views use this converter.
A converter reuses its client for multiple images. Disposing the converter
cancels its requests and disposes a client it created; an application-supplied
client remains owned by the application. Completed bitmap sources are not
disposed by the converter. The image owner should dispose its bitmap source
when it is no longer needed. These notifications use Avalonia's Image.Source
and logging rather than WPF BitmapImage download events.
Bind ViewModel content
DocumentsSource and AnchorablesSource accept observable collections of
application objects. With a view's x:DataType set to a ViewModel exposing
Documents and Tools, bind the sources and provide typed templates:
<DockingManager DocumentsSource="{Binding Documents}"
AnchorablesSource="{Binding Tools}">
<DockingManager.DataTemplates>
<DataTemplate DataType="{x:Type vm:EditorViewModel}"
x:DataType="vm:EditorViewModel">
<TextBox Text="{Binding Text, Mode=TwoWay}" AcceptsReturn="True" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PropertiesViewModel}"
x:DataType="vm:PropertiesViewModel">
<TextBlock Text="{Binding Description}" Margin="12" />
</DataTemplate>
</DockingManager.DataTemplates>
</DockingManager>
Here vm refers to the application's ViewModel namespace. The ViewModels derive
from ReactiveObject; editing properties must notify changes. Keep UI logic in
commands and services. Container styles can bind each generated layout item's
title and stable ContentId. A LayoutUpdateStrategy can also set these values
and choose its destination pane. There is no implicit mapping from a
ViewModel's property names to these layout properties.
Source additions, removals, replacements, and resets update the live layout. Moving a source entry preserves the user's tab order. Resetting a source clears all layout items of that type, including manually inserted items. Floating hosts resolve content templates from the manager's current scope, including ancestor templates.
Layout, DocumentsSource, AnchorablesSource, and LayoutUpdateStrategy
support Avalonia styles, conditional selectors, and compiled style bindings.
Removing a source style detaches its collection subscription and removes its
imported contents; clearing a local source value reveals the style underneath.
Strategy changes apply to subsequent insertion callbacks.
Each manager starts with its own local LayoutRoot, containing a LayoutPanel,
a LayoutDocumentPaneGroup, and an empty LayoutDocumentPane. Clear
DockingManager.LayoutProperty before supplying a layout through a style;
otherwise the constructor's local value has precedence. A layout can belong to
only one manager, so supply a separate instance for each styled manager.
Assigning Layout = null creates a fresh empty layout. This replacement raises
LayoutChanged without LayoutChanging; assigning a non-null value invokes
LayoutChanging during coercion, including reassignment of the current layout.
On the root model, assigning RootPanel = null creates a new LayoutPanel with
one empty LayoutDocumentPane. RemoveChild and ReplaceChild use the same
reset behavior. RootPanel, the four side properties, and Manager publish
PropertyChanging before the assignment and PropertyChanged afterward.
Observers can stop an operation by throwing; completed earlier changes remain.
Derived roots observe construction in right, left, top, bottom, then root-panel
order, with no root panel available before its construction assignment.
LeftSide, TopSide, RightSide, and BottomSide are nullable. Clearing a side
removes its displayed control and native pinned projection; assigning a side
restores it. This also works with TopSide="{x:Null}" and the other side names
in either XML namespace. Cleared sides write empty XML sections, which load as
default empty side models. Replaced sides retain their groups and contents for
reuse. Their public Parent and Root references also remain, with no parent or
membership notifications from the removal. The adapter releases unused native
ownership and subscriptions so changes to the former strip no longer affect the
root's pinned list.
Assigning a side that belongs to another root retains the old root's slot
reference while changing the side's parent. Multiple slots may reference the
same side. Its public Side value is resolved during a parent change; assigning
another slot of the same root does not recompute it. Native pinned ownership
uses one current slot independently of that retained value. Incoming parent
callback failures leave the root's new slot assignment in place and retain the
parent transition completed before the failure.
LayoutRoot.Children visits the root panel, floating windows, top/right/bottom/left
sides, then hidden tools, omitting null sides. ChildrenCount follows AvalonDock's
fixed-slot contract: five plus the floating-window and hidden-tool counts, even
when a side is null. ReplaceChild for hidden tools or floating windows sends
separate remove and add notifications. A removal callback failure stops before
insertion; native floating-host cancellation retains the original window.
OnLayoutChanged encloses ownership transfer, container/host updates, source
reattachment, and the LayoutChanged event. An override sees the new property
value before calling the base implementation and can suppress those side
effects by omitting that call. The constructor also invokes this hook with a
null old layout after creating the internal services; overrides must tolerate
that initial call. Property bindings use Avalonia's binding lifetime rules.
When an override redirects Layout or replays earlier arguments, the base hook
uses the current Layout for ownership, container attachment, pane/side
controls, floating hosts, and pinned previews. Source removal uses the supplied
old root; source import uses the supplied new root. Live source notifications
continue to use the current property. Replaying a foreign new-root argument does
not transfer that root's ownership.
Root property/update notifications are removed once from the supplied old root before source removal and registered on the supplied new root only after imports and presentation succeed. A failed import does not advance the base hook to panel/host setup or new notification registration. Repeated successful base calls retain independent registrations and application-handler ordering; these subscriptions retain neither obsolete roots nor unused managers. A root notification reads the current manager layout, even when its sender is an older callback argument. Native container cleanup continues to observe the presented root independently. A null new argument skips source import and notification attachment while current layout ownership and containers are retained.
Initial source attachment skips null values and values equal to content already in the layout. Repeated values within a new source remain separate items. Later add/replace notifications import every new entry, including nulls and duplicates. Initial attachment snapshots the source before invoking application strategies. One destination pane is chosen for the entire initial import or collection notification; changing focus inside a strategy affects the next batch. A fallback tool pane created during insertion is reused for the rest of that batch.
Each insertion consults the current LayoutUpdateStrategy separately for its
BeforeInsert… and AfterInsert… calls. Replacing the strategy in BeforeInsert…
directs the matching AfterInsert… call to the replacement; clearing it skips
that call. Returning true suppresses default insertion while still allowing
AfterInsert… to run. These rules apply to source imports, live collection
additions, Dock() when no return container is available, and a hidden tool's
Show() operation.
Initial source attachment retains its supplied layout throughout the batch.
Live collection callbacks receive the manager's current layout each time, even
if an earlier callback replaces it. The batch's default destination stays fixed;
an application that replaces the layout during insertion should place the item
itself and return true when the old destination is no longer appropriate.
When a tool source needs a new pane, its outer panel already contains the original panel when root property observers receive the replacement. If moving the original panel redirects the manager's layout, live source insertion uses that current root; initial attachment still uses its supplied root. A failure while publishing the outer panel preserves the completed subtree and stops before inserting the tool.
For LayoutAnchorable.Show(), a strategy can update PreviousContainer and
PreviousContainerIndex and return false to use the updated return location.
Clearing the return container leaves the tool hidden. AfterInsertAnchorable
runs before the temporary hidden-return history is cleared.
GetLayoutItemFromModel performs a lookup and accepts null. During a source
item's BeforeInsert… and AfterInsert… callbacks its container is not registered
yet. Set the layout model's title, identifier, and other metadata in those
callbacks. Registration and container styling happen after the after callback
returns and before the next source item is processed. Existing native controls
can be realized while a callback pumps the dispatcher; they receive the completed
container, its content, headers, and commands afterward. This includes floating
hosts created during insertion.
Initial source attachment registers handled items even if they remain detached. A live add/replace notification registers an item only when it belongs to this manager. Manually added items inside a source callback are also excluded from automatic container creation during that batch; a later ordinary layout attachment discovers them. Ordinary layout attachment creates containers eagerly, with documents before tools when assigning a layout. Container lookup retains removed items until deferred cleanup, while commands check their current model ownership. Callback exceptions release the insertion suspension and propagate; already completed model edits are retained.
A derived manager can observe or handle source notifications through
OnReceiveWeakEvent(Type, object, EventArgs):
using System;
using System.Collections.Specialized;
using Avalonia.Utilities;
using AvalonDock;
public sealed class SourceAwareManager : DockingManager
{
protected override bool OnReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(WeakEvent<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>) &&
e is NotifyCollectionChangedEventArgs change)
{
System.Diagnostics.Debug.WriteLine(change.Action);
}
return base.OnReceiveWeakEvent(managerType, sender, e);
}
}
The override runs before the layout update. Calling the base implementation
updates the current source; returning true without calling base lets the
override handle the event. The base returns false for an unknown manager type.
Returning false from an actual collection callback raises
InvalidOperationException, because that subscribed event was not handled.
Replacing a source inside the override makes the base ignore its old event.
Each completed source attachment adds a distinct weak registration, and each
detachment removes one. When both source properties refer to one collection,
each event is delivered twice and the document source takes precedence on both
deliveries, as in WPF. Repeated callback attachments also deliver separately.
Deliveries already in flight finish if an earlier callback detaches a source.
Use separate document and tool collections when each change should be processed
once.
WPF's CollectionChangedEventManager/IWeakEventListener are replaced by
Avalonia's WeakEvents.CollectionChanged and
IWeakEventSubscriber<NotifyCollectionChangedEventArgs>. The managerType
argument is the closed generic type shown above. Subscriptions use Avalonia's
weak listener support, so a retained source does not retain an unused manager.
See Avalonia's weak collection event.
OnDocumentsSourceChanged and OnAnchorablesSourceChanged detach the callback's
old source and import its new source. The supplied arguments govern these
operations, even when a derived manager replaces the property before calling
base. Such an override can import both the redirected source and the source in
the original callback. Suppressing or throwing before base leaves the previous
contents and subscriptions in place; a later callback detaches its own old
argument, not the last source that was successfully imported. Replaying a saved
callback likewise applies its arguments. Call base once for each property change
that should be applied.
Layout replacement uses the current source properties for detachment from the old root and import into the new root. Live collection changes consult the manager's current layout, including after a suppressed layout transition. Initial imports keep their supplied root and add their supplied source's subscription only after a successful batch. An exception during initial import preserves changes already made, releases the container-creation suspension, and does not add a listener. Distinct weak registrations preserve WPF's repeated attachment semantics without retaining obsolete collections or unused managers. See WPF's listener registration and delivery.
Style item containers
LayoutItemContainerStyle accepts an Avalonia ControlTheme. Its target is a
LayoutItem, LayoutDocumentItem, or LayoutAnchorableItem, and the container
is its own data context. Compiled bindings access the application ViewModel
through Model. For a document collection whose EditorViewModel exposes
Title, ContentId, CanClose, and CanFloat:
<DockingManager DocumentsSource="{Binding Documents}">
<DockingManager.LayoutItemContainerStyle>
<ControlTheme TargetType="LayoutItem" x:DataType="LayoutItem">
<Setter Property="Title" Value="{Binding ((vm:EditorViewModel)Model).Title}" />
<Setter Property="ContentId" Value="{Binding ((vm:EditorViewModel)Model).ContentId}" />
<Setter Property="CanClose" Value="{Binding ((vm:EditorViewModel)Model).CanClose, Mode=TwoWay}" />
<Setter Property="CanFloat" Value="{Binding ((vm:EditorViewModel)Model).CanFloat}" />
</ControlTheme>
</DockingManager.LayoutItemContainerStyle>
</DockingManager>
Use one-way bindings unless the ViewModel must also receive changes made by
Dock. The two-way CanClose binding above demonstrates that option. Titles,
identifiers, and capabilities affect the same native models that Dock renders.
Containers belong to the manager's logical tree, so themes can use manager and
ancestor resources, including DynamicResource, while content is floating.
Shared setters can live in a base theme referenced through BasedOn.
For different document and tool types, set LayoutItemContainerStyleSelector
to an AvalonDock.Controls.StyleSelector subclass. This is the counterpart of
WPF's StyleSelector; its result is an Avalonia ControlTheme:
using Avalonia;
using Avalonia.Styling;
using AvalonDock.Controls;
public sealed class WorkspaceStyleSelector : StyleSelector
{
public ControlTheme? EditorStyle { get; set; }
public ControlTheme? PropertiesStyle { get; set; }
public override ControlTheme? SelectStyle(object? item, AvaloniaObject container)
=> item switch
{
EditorViewModel => EditorStyle,
PropertiesViewModel => PropertiesStyle,
_ => null
};
}
Declare the themes and selector in XAML resources and assign the selector with
StaticResource. The selector receives the application object and its actual
item container. An explicit LayoutItemContainerStyle takes precedence.
Changing either manager property reapplies the style; replacing content
selects a theme for the new object and detaches the old typed bindings first.
A null selection removes the previously selected theme while preserving the
live layout state. Styles also apply to new source entries and hidden tools.
Clearing both manager style providers retains the themes already applied to existing containers. New containers use the current providers. To clear an existing theme, assign a non-null selector that returns null. A retained theme also follows replacement content after the previous typed bindings are released.
The protected style callbacks read the current manager settings, including when replaying captured change arguments. An override which omits base retains the existing container styles; new source containers still use current settings. Default commands are cleared before selecting a theme and available defaults are restored afterward. Application commands remain intact. If the selector throws, the previous theme remains and defaults stay cleared until a successful style application. A reentrant manager style change runs immediately; the outer selector's result is then applied when it returns.
The callback visits document and tool snapshots in that order. Each existing model attachment refreshes the registered container list in registration order, so selectors can run multiple times for a single property change. Containers awaiting deferred removal still participate until the dispatcher performs their cleanup.
Native layout additions also reapply current styles to existing registered containers, including after a manager override suppressed an earlier style callback. Source insertion batches instead register newly imported containers after their insertion callbacks; adding an item within that batch does not itself restyle the existing containers.
Each successful style attachment registers a distinct pair of root element listeners after the document and tool callbacks finish. An override that skips base, or a callback that throws before completion, adds no registration. Native event delivery preserves the order of these registrations and application handlers. New registrations created during a notification participate from the next event onward.
Layout replacement removes one registration from the manager's current layout
property, then attaches again after creating its containers. A retained previous
root can therefore still send notifications. Its listener refreshes the current
layout and excludes foreign content added to the old root. Returning to a root
removes one existing registration before adding its replacement. Weak keys and
weak listeners preserve these callback contracts without retaining obsolete
roots or unused managers; native Updated observation stays scoped to the
presented root. Initial attachment processes document callbacks before taking
the tool snapshot, and starts root membership observation only after successful
completion.
Command setters bind application ReactiveCommand properties through Model,
just like metadata. For example, add this setter to the document theme when
EditorViewModel.CloseCommand implements the application's close workflow:
<Setter Property="CloseCommand" Value="{Binding ((vm:EditorViewModel)Model).CloseCommand}" />
The tab close button, Close menu item, and middle-click then invoke that
command. Replacing the command or changing its CanExecute updates the UI.
Replacing a theme or restoring XML releases its bindings without disposing
application commands; available defaults return when the style stops supplying
a command.
Customize document and tool context menus
DocumentContextMenu and AnchorableContextMenu accept Avalonia ContextMenu
instances. Each open menu receives the relevant LayoutItem as its data context,
so compiled bindings can use container commands and metadata directly:
<DockingManager>
<DockingManager.DocumentContextMenu>
<ContextMenu x:DataType="LayoutItem">
<MenuItem Header="Close" Command="{Binding CloseCommand}" />
<MenuItem Header="Float" Command="{Binding FloatCommand}" />
</ContextMenu>
</DockingManager.DocumentContextMenu>
<DockingManager.AnchorableContextMenu>
<ContextMenu x:DataType="LayoutAnchorableItem">
<MenuItem Header="Hide" Command="{Binding HideCommand}" />
<MenuItem Header="Auto Hide" Command="{Binding AutoHideCommand}" />
</ContextMenu>
</DockingManager.AnchorableContextMenu>
<!-- Add a LayoutRoot or bind the content sources here. -->
</DockingManager>
The enclosing view declares the default Avalonia XML namespace and x namespace,
as in the layout example above. No avalondock prefix is required. Use Model
to reach application content or LayoutElement to reach the public layout node.
A tool docked in a document pane uses DocumentContextMenu, with its existing
LayoutAnchorableItem as the binding context.
Tool menus also apply to title bars, their menu buttons, and auto-hide strips. Both properties work in floating windows. Keyboard context-menu requests from pane content use the active item, while a child control's own context menu or flyout takes precedence, including when its opening is canceled. Title-bar menus track active-tool changes; tab menus keep the requested tab's item.
DockingManager.OnKeyDown(KeyEventArgs) handles Shift+F10 on key press. Its base
implementation reads the current ActiveContent, follows its visual parents to
the public document/tool content control, and opens the appropriate manager menu
at that control's origin. The menu receives that control's LayoutItem, which
can be null for an application-created content control. An override can replace
the menu before calling base, or suppress the base call. Cancellation and
suppression do not produce another docking-menu request on key release. A menu
opened by the shortcut stays open through that release. Child-owned menus and
flyouts retain their platform shortcuts; the Apps key follows Avalonia's ordinary
context-request route.
The adapter theme supplies separate default menus per manager. Assigning null
(or {x:Null} in XAML) disables the corresponding menu. Replacing a menu closes
the old invocation; clearing the local property value restores the theme default.
Application menu opening can be canceled using Avalonia's Opening event.
Closing, detaching, or replacing the layout clears the invocation's data context
and placement references. The application retains ownership of its menu items
and commands.
Use the public menu controls
AvalonDock.Controls includes ContextMenuEx, MenuItemEx, DropDownButton,
and DropDownControlArea. The manager's default document and tool menus use
ContextMenuEx and MenuItemEx; custom menus can use either these controls or
ordinary Avalonia menus. ContextMenuEx generates MenuItemEx containers for
data items and refreshes a bound ItemsSource when opened, including a computed
snapshot that changed without a property notification while the menu was closed.
Explicit menu items and separators retain their identity.
MenuItemEx.IconTemplate builds an icon that inherits the item's data context.
IconTemplateSelector receives the current Icon and the menu item. A selector
takes precedence over IconTemplate; a null result preserves the current icon.
Changing a template, selector, or non-null icon reevaluates the template.
Explicitly clearing Icon leaves it null; clearing the template keeps the icon
already created. Use compiled templates with the appropriate x:DataType.
DropDownButton opens DropDownContextMenu below the button on click or keyboard
activation, then toggles IsChecked, raises Click, and executes the current
command when CanExecute allows it. Repeated protected clicks keep the existing
menu open and retain normal two-state or three-state toggling; closing the menu
resets IsChecked to false. A handled Click does not suppress the command.
DropDownControlArea opens the same property during an unhandled right-button
release's tunnel route and leaves Handled unchanged. Nested areas and child
menus/flyouts can therefore run their own input handling afterward. Both controls
copy DropDownContextMenuDataContext into the
menu when opening it. For example, with a LayoutItem as the view's data context:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ad="http://schemas.xceed.com/wpf/xaml/avalondock"
x:DataType="LayoutItem">
<ad:DropDownButton Content="Actions" DropDownContextMenuDataContext="{Binding}">
<ad:DropDownButton.DropDownContextMenu>
<ContextMenuEx x:DataType="LayoutItem">
<MenuItemEx Header="Activate" Command="{Binding ActivateCommand}">
<MenuItemEx.IconTemplate>
<DataTemplate x:DataType="LayoutItem">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</MenuItemEx.IconTemplate>
</MenuItemEx>
</ContextMenuEx>
</ad:DropDownButton.DropDownContextMenu>
</ad:DropDownButton>
</Window>
Avalonia also has a DropDownButton, so that name needs disambiguation in a
view using Avalonia's default XML namespace. The other three menu controls and
the layout API remain available unprefixed. To use the compatibility button
unprefixed, give a dedicated control or resource file the historical namespace
as its root default namespace:
<DropDownButton xmlns="http://schemas.xceed.com/wpf/xaml/avalondock"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:av="https://github.com/avaloniaui"
x:DataType="LayoutItem"
Content="Actions" DropDownContextMenuDataContext="{av:Binding}">
<DropDownButton.DropDownContextMenu>
<ContextMenuEx x:DataType="LayoutItem">
<MenuItemEx Header="Activate" Command="{av:Binding ActivateCommand}" />
</ContextMenuEx>
</DropDownButton.DropDownContextMenu>
</DropDownButton>
Avalonia permits XML namespace declarations only on the file's root; a nested
default-namespace override cannot resolve this collision. In C#, when importing
both control namespaces, use using DropDownButton = AvalonDock.Controls.DropDownButton;.
Closing, replacing, canceling, or detaching a dropdown clears its invocation
data and placement references and
preserves application items, commands, icon/header content, and templates for
reopening. The ordinary ContextMenu and its binding are restored before the
dropdown popup opens, so the preview hook does not cause Avalonia to request the
same dropdown again during release bubbling. Cancelable Opening, resource
updates, and reentrant reopening remain supported. A canceled opening leaves
the button unchecked. Application templates retain their normal Avalonia resource scope:
retaining a compiled template can retain its declaring XAML root. Prefer shared
resource dictionaries for templates that must outlive a view.
Derived controls can override ContextMenuEx.GetContainerForItemOverride() and
OnOpened(RoutedEventArgs), MenuItemEx.OnIconTemplateChanged and
OnIconTemplateSelectorChanged, and DropDownButton.OnDropDownContextMenuChanged.
The property hooks receive AvaloniaPropertyChangedEventArgs.
DropDownControlArea exposes OnMouseRightButtonDown(PointerPressedEventArgs)
and OnPreviewMouseRightButtonUp(PointerReleasedEventArgs) over Avalonia's
bubbling press and tunneling release routes. Call the base implementation to
retain the default behavior. Explicit protected-hook calls are not filtered by
control enablement or descendant menu ownership; normal pointer delivery still
uses Avalonia's enabled-control rules. The click ordering follows WPF's
ToggleButton
and ButtonBase
contracts using Avalonia events, properties, and commands.
Select content templates
LayoutItemTemplate applies an explicit Avalonia IDataTemplate to application
content in both docked and floating panes. For example, when every document
uses the same ViewModel type:
<DockingManager DocumentsSource="{Binding Documents}">
<DockingManager.LayoutItemTemplate>
<DataTemplate x:DataType="vm:EditorViewModel">
<TextBox Text="{Binding Text, Mode=TwoWay}" AcceptsReturn="True" />
</DataTemplate>
</DockingManager.LayoutItemTemplate>
</DockingManager>
For different content types, use LayoutItemTemplateSelector. The WPF selector
counterpart is AvalonDock.Controls.DataTemplateSelector: override
SelectTemplate(object?, AvaloniaObject) and return an Avalonia IDataTemplate.
The arguments are the application content and its actual ContentPresenter.
The selector can refer to compiled templates declared in XAML resources:
using Avalonia;
using Avalonia.Controls.Templates;
using AvalonDock.Controls;
public sealed class WorkspaceTemplateSelector : DataTemplateSelector
{
public IDataTemplate? EditorTemplate { get; set; }
public IDataTemplate? PropertiesTemplate { get; set; }
public override IDataTemplate? SelectTemplate(object? item, AvaloniaObject container)
=> item switch
{
EditorViewModel => EditorTemplate,
PropertiesViewModel => PropertiesTemplate,
_ => null
};
}
Set LayoutItemTemplateSelector to an instance of this selector. An explicit
LayoutItemTemplate takes precedence. When the selector returns null, normal
manager, ancestor, and application data-template lookup applies. Inline
controls and Dock template content retain Dock's native content handling.
Changing either manager property updates existing presenters; changing content
selects the appropriate template for the new object. Recycling is limited to
controls built by the same selected template. An explicit template can also
display null content.
Customize the document list menu
Each native document tab strip has an Open documents button. Its menu lists
that pane's ChildrenSorted, including tools docked as documents. Collection
changes refresh an open menu without rearranging the tabs. Selecting an entry
uses its current LayoutItem.ActivateCommand; command enablement and the layout
node's IsEnabled apply to the entry.
The manager exposes separate templates for the entry header and icon. Their
generated controls use the public LayoutContent as the data context, including
tools docked as documents.
For example, these properties go inside a DockingManager using Avalonia's
default XML namespace:
<DockingManager.DocumentPaneMenuItemHeaderTemplate>
<DataTemplate x:DataType="LayoutContent">
<TextBlock Text="{Binding Title}" FontWeight="SemiBold" />
</DataTemplate>
</DockingManager.DocumentPaneMenuItemHeaderTemplate>
<DockingManager.IconContentTemplate>
<DataTemplate x:DataType="LayoutContent">
<Image Source="{Binding IconSource}" Width="16" Height="16" />
</DataTemplate>
</DockingManager.IconContentTemplate>
DocumentPaneMenuItemHeaderTemplateSelector coerces an explicit non-null menu
header template to null. A null assignment instead falls back to
DocumentHeaderTemplate, including when a menu selector exists. That non-null
fallback is rendered before consulting the selector. Later document-header
changes do not refresh the cached fallback: assign the menu template again or
call CoerceValue(DocumentPaneMenuItemHeaderTemplateProperty). If neither provides a
template, the menu shows the default title text. Assigning a document header
selector also supplies the menu selector if the latter is unset.
Document entries are public MenuItemEx controls. IconContentTemplateSelector
receives the entry's current Icon and the owning MenuItemEx. The current icon
can be null or a previously generated control; use the container's DataContext
for the public layout model. Returning null preserves the icon, including when
the explicit template changes. The selector leaves IconContentTemplate intact,
so clearing the selector applies that template. Icon templates create controls
with an inherited entry data context. The default manager theme provides an
IconSource template, also exposed through IconContentTemplate.
LayoutContent.IconSource and LayoutItem.IconSource use Avalonia IImage.
Changes to templates and model metadata update an open menu. The same menu
behavior applies in native floating document windows, and layout replacement
closes the old invocation and releases its generated presentation.
Size panes and splitters
DockWidth and DockHeight accept Avalonia GridLength values, including pixel
sizes such as 240, Auto, and star sizes such as * or 2*. The manager exposes the
split gap separately for each layout direction:
| Manager property | Applies to | Default |
|---|---|---|
GridSplitterWidth |
Width between horizontally arranged panes | 6 device-independent pixels |
GridSplitterHeight |
Height between vertically arranged panes | 6 device-independent pixels |
These properties can be set, bound, or styled. For example, with the default Avalonia XML namespace:
<DockingManager GridSplitterWidth="8" GridSplitterHeight="6">
<LayoutRoot>
<LayoutPanel>
<LayoutAnchorablePane DockWidth="240">
<LayoutAnchorable Title="Properties" />
</LayoutAnchorablePane>
<LayoutDocumentPane>
<LayoutDocument Title="Editor" />
</LayoutDocumentPane>
</LayoutPanel>
</LayoutRoot>
</DockingManager>
Each nested or floating panel calculates pane proportions using its own available
space and the configured splitter gap. Pixel panes keep their requested size
when the host or gap changes; an all-pixel split can leave unused space or extend
beyond the viewport. Auto measures the existing pane's content and chrome and
remeasures when content or tab selection changes. Star panes divide the space
remaining after fixed sizes, intrinsic sizes, and minimums. DockMinWidth and
DockMinHeight start at 25 and constrain rendering without replacing requested
lengths. Collapsed return panes and adjacent hidden splitters consume no space.
Changing group orientation selects the corresponding manager splitter setting.
Minimum setters raise PropertyChanging and PropertyChanged using the public
DockMinWidth or DockMinHeight name. Native size edits project the corresponding
public changed notification without leaking MinWidth or MinHeight. Equal
values are ignored before validation, so reassigning a negative value restored
from XML is allowed; a different negative value raises ArgumentException
without a parameter name. Repeated NaN assignments each raise both notifications.
Reentrant callbacks and their exceptions retain the same notification phases.
Native splitter previews commit public pane dimensions on release and cancel
when pointer capture is lost. Pixel lengths retain their units, a resized
star/star pair retains its combined weight, and resizing Auto converts it to
pixels. XML otherwise preserves Auto and restores independent pane lengths.
The adapter presents LayoutGridResizerControl, derived from Dock's native
ProportionalStackPanelSplitter, for proportional pane splits. Style its
BackgroundWhileDragging and OpacityWhileDragging properties to customize
the preview. The defaults are black and 0.5; use opacity between zero and one.
Changes also update an open drag preview. Both XML namespace forms
are supported. Ordinary native Dock splitters retain their accent resource and
opacity of one. Auto-hide previews also expose this public resizer, using
Dock's pinned layout and bounds instead of proportional pane dimensions.
Derived positionable groups can override OnDockWidthChanged() and
OnDockHeightChanged(). The hooks run after property-changing and property-changed
notifications for a changed value, including committed native splitter sizes
and external native proportion changes. Assigning an equal value does nothing.
Mapping requested sizes into native proportions during measurement does not
invoke these hooks. XML restores stored lengths without these notifications or
callbacks.
LayoutAnchorablePaneGroup uses these hooks to pass a changed pixel length to
its only child's corresponding dimension, including through nested single-child
tool groups. This applies to either axis regardless of orientation. Auto and
star changes, changes with zero or multiple children, and subsequent child
additions do not copy sizes. Plain panels and document groups do not copy them
either. Call the base hook when overriding a tool group to preserve this behavior.
The pane-group rebuild policy above can convert pixel lengths to stars. Set
pixel dimensions after the first layout when exercising runtime splitter sizing;
use a LayoutPanel with a star-sized sibling for a fixed-width tool region.
The settings belong to one manager and update its existing floating windows. XML restoration and view reattachment retain them as manager configuration; they are not serialized as layout metadata. Native Dock controls outside the adapter retain their default splitter size.
Customize tab headers and window titles
Header templates receive the public layout node. Content templates receive the
application object stored in that node's Content property.
| Manager property | Native presentation | Template data |
|---|---|---|
DocumentHeaderTemplate |
Document tabs, docked or floating | LayoutContent (documents and tools docked as documents) |
AnchorableHeaderTemplate |
Tool tabs and auto-hide strips | LayoutAnchorable |
AnchorableTitleTemplate |
Selected tool's title bar, including floating panes and auto-hide previews | LayoutAnchorable |
DocumentTitleTemplate |
Floating document window caption | LayoutDocument |
For example, add these properties to a manager declared in the default Avalonia XML namespace:
<DockingManager.DocumentHeaderTemplate>
<DataTemplate x:DataType="LayoutContent">
<TextBlock Text="{Binding Title}" FontWeight="SemiBold" />
</DataTemplate>
</DockingManager.DocumentHeaderTemplate>
<DockingManager.AnchorableHeaderTemplate>
<DataTemplate x:DataType="LayoutAnchorable">
<TextBlock Text="{Binding Title}" FontStyle="Italic" />
</DataTemplate>
</DockingManager.AnchorableHeaderTemplate>
<DockingManager.AnchorableTitleTemplate>
<DataTemplate x:DataType="LayoutAnchorable">
<TextBlock Text="{Binding Title}" FontWeight="SemiBold" TextTrimming="CharacterEllipsis" />
</DataTemplate>
</DockingManager.AnchorableTitleTemplate>
DocumentHeaderTemplateSelector, AnchorableHeaderTemplateSelector, and
AnchorableTitleTemplateSelector accept
AvalonDock.Controls.DataTemplateSelector. SelectTemplate receives the
layout node and the actual presenter. A document-header selector should
also handle LayoutAnchorable when tools can be docked into document panes.
The base selector callbacks clear the explicit template and its local binding.
A subsequent template assignment is coerced to null while a selector is set.
A subclass can suppress the base callback; an existing non-null template then
renders before the selector. A null selector result uses a title header.
Clearing the selector does not restore a previously coerced template: assign it
again or call CoerceValue explicitly. The document-title and tool-header base
callbacks always clear the template for a new selector; document-header,
tool-title, and menu-header callbacks clear only a non-null effective template.
Style bindings survive beneath the local override. Application calls to
Avalonia's inherited SetValue retain bindings; explicitly dispose a binding
when the application intends to replace it permanently.
Content, tab/header, title, and menu presenters receive each template or selector value after its protected change callback returns successfully. A throwing callback leaves that provider's previously delivered value in existing presenters; completed nested changes remain applied. Updating the other provider does not import a value whose callback failed. Newly created presenters read the current manager properties. Nested callbacks that already delivered the final settings do not rebuild those presenters a second time. A document-header change does not refresh a menu whose coerced fallback template stayed unchanged.
Replaying a selector callback uses its supplied NewValue to decide whether to
clear the explicit template. Document-menu selector fallback instead reads the
current document-header selector at the base call and preserves an existing
non-null menu selector.
Changing a manager header property updates existing docked, floating, and
auto-hide headers. Compiled bindings observe live title and metadata changes.
Native document/tool tab containers and custom public tab controls apply the
model's IsEnabled and ToolTip. Disabled tabs reject pointer selection.
Default tool headers include the current IconSource in docked, floating, and
auto-hide locations; clearing the icon removes its spacing. Default document
headers remain text-only. A custom header template replaces the entire default
header, so it should bind the icon explicitly when needed.
Native tab selection, activation, and auto-hide preview input continue to work.
XML restoration releases old header content and binds the replacement nodes.
Tool titles follow the selected tab independently of its tab header. The title template and selector use the same precedence rules as header templates; clearing both returns to the default title text. Runtime changes reach existing tool title bars, including floating panes and open auto-hide previews. Native context menus, title-bar buttons, and input handling remain available. Source removal and XML replacement release the old title's model and manager references.
Floating document captions use DocumentTitleTemplate independently of tab
headers. For example:
<DockingManager.DocumentTitleTemplate>
<DataTemplate x:DataType="LayoutDocument">
<TextBlock Text="{Binding Title}" FontWeight="SemiBold" TextTrimming="CharacterEllipsis" />
</DataTemplate>
</DockingManager.DocumentTitleTemplate>
DocumentTitleTemplateSelector receives the window's public RootDocument
and its actual presenter. Selector precedence and fallback follow the title
rules above. Replacing the root document, loading XML, or changing templates
updates the caption. The native window's text title also follows document
metadata, independently of the caption template.
Document hosts use a client caption with the manager's document context menu, the current item close command, and maximize/restore controls. Caption dragging uses Dock's native window drag helper and factory cancellation. Double-clicking the caption maximizes or restores the window; buttons and other focusable caption content retain their own input. Reattaching a manager recreates its floating hosts while retaining layout models and application content. Keep a stable data context for source bindings while the manager is detached.
Observe docking and floating operations
Native drag destinations follow the upstream floating-window rules.
LayoutDocumentFloatingWindow contains one document and does not accept
incoming drops. A LayoutAnchorableFloatingWindow accepts tools from its own
manager, including tools dragged from another floating tool window. Its native
splits retain typed LayoutAnchorablePaneGroup wrappers, item containers, and
content through XML restoration and docking back.
AllowMixedOrientation controls document-pane split indicators and execution.
With the default False, a document group with more than one visible child
offers splits along its current orientation. Set it to True to allow either
axis. Empty siblings do not restrict the split axis. Native splits within a
document group retain LayoutDocumentPaneGroup wrappers for new groups.
These checks run before layout mutation through the native factory's optional
IDockDropPolicy contract. Direct factory edits bypass drag validation; native
grouped documents and generic or mixed panels are projected as
LayoutGroupFloatingWindow. Its RootPanels collection contains the original
native-backed pane and group adapters. Native group floating records return
panes for documents and tools; floating the entire root first installs a valid
replacement main panel. These additional shapes use a Dock XML extension,
described under serialization below.
DockingManager exposes four bubbling routed events: PreviewFloat, Floated,
PreviewDock, and Docked. Their Source is the public LayoutContent, while
the handler's sender is the manager or ancestor handling the event. Preview
notifications run before native movement; completion notifications run after
the operation. The protected RaisePreviewFloatEvent/RaiseFloatedEvent and
protected-internal RaisePreviewDockEvent/RaiseDockedEvent methods let derived
managers extend these notifications.
A workspace service can subscribe without putting handlers in view code-behind:
using Avalonia.Interactivity;
using AvalonDock.Layout;
manager.Docked += OnDocked;
void OnDocked(object? sender, RoutedEventArgs args)
{
if (args.Source is LayoutContent content)
Console.WriteLine($"Docked: {content.ContentId}");
}
// Unsubscribe when the workspace service detaches.
manager.Docked -= OnDocked;
The events cover layout methods, float/dock commands, native menus, and native drag execution. A group operation raises one pair: floating uses its selected content, and docking uses its first content. Native validation does not raise events or mutate the layout. Internal moves in a split or group operation do not produce additional pairs. Reordering tabs and restoring a previous floating pane without creating a new window are silent, matching the upstream paths. Document tab-group commands are also silent. Floating an auto-hidden tool raises its preview before Dock unpins it.
Despite the Preview names, these events bubble rather than tunnel. Setting
Handled affects event routing and does not cancel docking or floating. Native
capabilities still apply, and a rejected floating attempt does not raise
Floated. Exceptions release the operation scope, and changing the manager's
layout during a preview prevents stale completion notifications.
Override control lifecycle hooks
Derived DockingManager and LayoutFloatingWindowControl classes can override
OnInitialized(EventArgs). Avalonia invokes the hook once when native
initialization completes, before the public Initialized event. A floating
window may initialize while its constructor installs its styles. Hiding and showing
a window or reattaching a manager does not initialize it again. Call the base hook
to retain native initialization.
DockingManager.OnApplyTemplate() is a public virtual hook invoked after each
Avalonia template application. Its base implementation refreshes the public root,
side, and auto-hide presentations. It reuses existing presentations while their
models are unchanged; calling the hook again does not replace them.
Floating controls expose both the AvalonDock OnClosing(CancelEventArgs) hook
and Avalonia's OnClosing(WindowClosingEventArgs) overload. Native closure routes
through the latter to the former, preserving the same argument object and native
close reason. Set Cancel before calling base to veto the request before content
mutations. The base hook raises Avalonia's Closing event, then lets Dock process
factory and content cancellation. Internal cleanup also uses this path, so a
canceled request preserves the registered host until closing is retried.
Use Cancel for a veto; a thrown hook exception retains Avalonia's native
close/cleanup semantics and can still complete window closure.
These hooks belong to custom controls. Ordinary application close policy can continue using the model commands and cancelable closing notifications.
OverlayWindow also exposes OnClosing(CancelEventArgs), preserving the native
arguments and the Avalonia overload. A canceled close leaves its content and
native selector projections available; successful closure releases them.
Protected constructors on the document/tool floating controls and overlay allow
subclasses to participate through Dock's host and adorner-window factories.
Configure native floating captions
ShowSystemMenu defaults to true and can be set in either XML namespace:
<DockingManager ShowSystemMenu="False" />
This disables the Windows system-menu fallback for native caption right-clicks.
An available DocumentContextMenu, or AnchorableContextMenu for a single-pane
tool window, takes precedence regardless of this setting. Its data context is
the current LayoutItem, and it opens at the physical screen position supplied
by Windows, including negative monitor coordinates. Canceling that menu's
Opening event also consumes the request. Multi-pane tool windows without a
single content item use the system fallback.
Floating controls expose the protected upstream
FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
hook through Avalonia's Win32Properties callback. Registration starts at the
first opening, after derived constructor state is available, and ends on close.
Call base to retain caption activation and menu routing. Avalonia and Dock
retain movement, resizing, hit testing, and execution of native window commands.
The managed caption templates and menus continue to work on other platforms;
those platforms do not deliver Win32 messages.
For the Windows fallback, a composed service uses source-generated interop to
retrieve the window's system menu
and display it with TrackPopupMenu. The operating system supplies its labels.
The service updates state-dependent commands and enables Close when the current
content can close or hide. Selected commands return through WM_SYSCOMMAND to
the existing native host and its cancellation hooks. Windows-specific native
styles retain the system-menu handle; a decoration theme keeps Dock's caption
and resize controls as the visible chrome.
Applications can override the additional protected
bool ShowSystemMenu(PixelPoint position) hook to replace this fallback.
The position uses physical screen coordinates. Return true for a consumed
request, including dismissal; the base implementation returns false on a
non-Windows or non-native host. Custom layout-item menus still take precedence.
Headless tests cover message policy, coordinates, override dispatch, and closing lifecycle. Windows 11 ARM64 desktop checks exercise real window handles and callback results, registration across hide/show, custom/canceled menus, native system-menu display, close vetoes, and tool hiding/restoration. Those earlier desktop checks predate the corrected host/content-close distinction below; the new close policy has WPF-reference and Avalonia Headless coverage. Selecting native menu items through real pointer/keyboard input and broader desktop dragging, resizing, and focus validation remain open.
Inspect native floating-window controls
DockingManager.FloatingWindows enumerates LayoutFloatingWindowControl
instances. These derive from Dock's HostWindow and are the actual Avalonia
windows displaying the floating layouts. Documents use
LayoutDocumentFloatingWindowControl; tool groups use
LayoutAnchorableFloatingWindowControl. Their ILayoutControl.Model is the
same public floating model found in LayoutRoot.FloatingWindows.
using AvalonDock;
using AvalonDock.Controls;
foreach (LayoutFloatingWindowControl window in manager.FloatingWindows)
Console.WriteLine($"{window.Model}: maximized={window.IsMaximized}");
manager.LayoutFloatingWindowControlCollectionChanged += OnFloatingWindowsChanged;
void OnFloatingWindowsChanged(object? sender,
LayoutFloatingWindowControlCollectionChangedEventArgs args)
{
Console.WriteLine(args.CollectionChangedEventArgs.Action);
}
// Unsubscribe when the workspace service detaches.
manager.LayoutFloatingWindowControlCollectionChanged -= OnFloatingWindowsChanged;
The event wraps the collection action, indices, and actual added or removed
controls. Canceling a close leaves the control registered. Removing a floating
model, closing its host, layout replacement, and detaching the manager remove
closed hosts. Reattachment
can create new control instances for the same persistent floating models, so
keep application state on the models. Native drag and window-state changes
update IsDragging and IsMaximized.
Hiding all tools hides their floating window while retaining the same control,
resources, geometry, item commands, and model. LayoutAnchorable.Show() restores
that host. Empty panes referenced by hidden or floating content keep the host
available for restoration. These visibility changes do not add or remove manager controls
or duplicate native host registration. Hidden models loaded from XML or a
reattached view receive new unshown controls and become visible when restored.
Hidden windows are excluded from native docking hit tests and the optional
bring-windows-forward behavior during dragging.
An explicitly created window that has never been shown remains under the
caller's control until Show() is requested.
RootDocumentLayoutItem resolves a document host's current container.
SingleContentLayoutItem follows the selected content when a tool host has one
visible pane and becomes null when it has multiple panes. Its default binding
is compiled and uses LayoutItemFromLayoutModelConverter, also available under
AvalonDock.Converters and both supported XML namespaces. Application values
and bindings can override this default through Avalonia property precedence.
Use CreateFloatingWindow when you need to configure a window before displaying
it. The method moves a document or tool into a new native floating layout and
returns its unshown control. PreviewFloat and Floated describe the layout
move; native WindowOpened fires when the window is shown.
LayoutFloatingWindowControl? window =
manager.CreateFloatingWindow(document, isContentImmutable: true);
if (window is not null)
{
window.Title = "Document workspace";
window.Show();
}
The method returns null when floating is rejected. Documents must belong to a
pane; standalone panes and content from another manager can be transferred into
this manager. An unattached or auto-hidden tool is first placed in a temporary
pane. Unlike LayoutContent.Float(), creation always requests a new window.
It works before the manager is loaded, and nested creation requests keep their
own options. Set content floating bounds before calling it to choose the initial
position and size.
isContentImmutable prevents replacing the window's Content after it loads;
it does not make the hosted layout or document read-only. With false, custom
window content uses normal Avalonia content rendering. Derived floating controls
can also use the protected constructor's immutability option.
LayoutAnchorableFloatingWindowControl.CloseWindowCommand and
HideWindowCommand operate on all tools in that window. Their CanExecute
queries check each model's capability and current container command. Execution
forwards the caller's parameter without repeating those queries. Check
CanExecute before calling Execute directly:
// toolWindow is a LayoutAnchorableFloatingWindowControl.
if (toolWindow.HideWindowCommand.CanExecute(null))
toolWindow.HideWindowCommand.Execute(null);
They can also be used in compiled bindings with the floating control as the binding scope's data context:
<StackPanel x:DataType="LayoutAnchorableFloatingWindowControl">
<Button Content="Close tools" Command="{Binding CloseWindowCommand}" />
<Button Content="Hide tools" Command="{Binding HideWindowCommand}" />
</StackPanel>
Enablement follows membership, capabilities, command replacement, and application
CanExecuteChanged notifications. Execution snapshots the contents so a command
can remove its own tool; subsequent tools still use their current commands.
A missing model, root, container, or command fails direct execution; errors
propagate synchronously and stop later tools. An empty model with a root but no
manager has no child command to execute. An unparented LayoutRoot used as the
model has no enclosing root and fails direct execution. Capability queries
safely return false for missing model/root/manager context.
Protected CanClose, CanHide, DoHide, and the tool-window commands read the
current virtual Model property. A document host filters document descendants;
a tool host filters tool descendants, including when an override supplies a
root or pane. Command queries and execution update subscriptions when that
model changes, releasing the previous model's items and commands. A command
callback can replace Model without redirecting an execution already in progress.
Typed caption activation and RootDocumentLayoutItem continue to use the
constructor's floating model, as upstream does.
Content closing/hiding cancellation is preserved by those commands.
After successful hiding or the base floating OnClosed lifecycle, focus is
requested on the owner window. Without an owner, the current virtual model's
manager supplies its first focusable visual descendant. Only that target is
attempted: a hidden/disabled target or a failed focus request does not select a
later descendant, and the manager itself is excluded. A callback can redirect
the model or focus; callback failures prevent this final focus request. Closing
restores focus after Closed, before typed model subscriptions are removed.
These ordering rules have Headless coverage; desktop activation remains subject
to Avalonia and the platform, with the current desktop checks deferred.
Window.Close() follows a separate WPF contract: it checks the protected
CanClose hook, which consults current commands without executing them. If close
is unavailable, it marks the close canceled before checking CanHide and calling
DoHide when allowed. These hooks observe the canceled arguments, and their
changes to cancellation are preserved. Hiding therefore precedes the public Closing event;
cancel hiding through the content's Hiding event. A Closing subscriber can
change the cancellation result before Dock's factory veto is evaluated.
An eligible Window.Close() closes only the host and preserves its floating model
and content. It raises no content Closing/Closed events. To remove content, use
LayoutContent.Close(), the layout item's CloseCommand, or the tool host's
CloseWindowCommand. Unloading the manager also preserves models; successful
layout replacement detaches the old floating models without closing their content.
Closed hosts release command subscriptions without disposing application commands.
Closing the owning application window closes its floating hosts without invoking
child close/hide commands or child Closing handlers, matching
WPF owned-window closing.
The owner's own Closing handler can still cancel shutdown. Successful owner
closure releases the native hosts and preserves their layout models.
Dock's optional IDockWindowClosePolicy lets LayoutFactory preserve a compatible
host's layout while retaining window cancellation and closed notifications. It
bypasses native content-close commands, releases the old host, and allows a new
host when the preserved layout is presented again. Ordinary Dock factories keep
their existing behavior.
The base OnClosed(EventArgs) captures the current model's root before callbacks.
Dock first captures the native model, factory, and close policy and marks the host
closed. Its HostWindow.OnClosedCore hook then lets the adapter remove the host
from the manager and collect that captured root before raising Closed. An empty
floating model can therefore be absent during Closed, while the original native
factory still receives its later WindowClosed notification. A callback that
redirects the virtual Model cannot redirect this collection. There is no second
garbage collection after Closed; empty panes added by that callback remain.
Both typed floating controls keep observing their constructor model throughout
the base close lifecycle. Removing its document or tool root in a Closed
callback can still mark the request internal. Their explicit OnClosed overrides
unsubscribe after the base call returns successfully. The tool override then
clears local binding expressions for IsVisible and SingleContentLayoutItem,
and releases its owned commands. Those bindings and commands remain available
inside Closed. Raw observable bindings retain Avalonia ownership; use compiled
bindings or ToBinding() for expression cleanup. Suppressing a base call or
throwing from a callback stops later lifecycle stages and their cleanup.
If an empty floating model was collected before Closed or WindowClosed,
reusing it for a replacement host requires explicitly adding it back to
root.FloatingWindows after assigning its new content. The old host's deferred
cleanup preserves an already registered replacement host.
Native Closing and factory WindowClosing vetoes preserve the native model link
and tracking. Public registration follows the deferred lifecycle below. Explicitly
removing or replacing an attached document or nonempty tool model leaves the
original collection entry if closing is canceled. Transferring that entry to another
manager throws InvalidOperationException when its current host vetoes removal;
the original manager retains ownership. Retry after the veto is lifted.
Content already closed or docked stays in its resulting state. Internal closure
is queued once, and Layout.CollectGarbage() does not retry a canceled request.
Call the retained host's Close() after lifting the veto to retry closure.
Derived floating controls can inspect the protected CloseInitiatedByUser
property. It starts true and becomes false when internal model cleanup, layout
replacement, or native window exit requests closure; cancellation does not reset
an internal request. Hiding and ordinary view unload preserve its value. As in
upstream, this records the internal-close request, not whether a human physically
clicked a button. Inspect it in Avalonia's OnClosing(WindowClosingEventArgs) or
OnClosed(EventArgs) overrides.
The protected OnStateChanged(EventArgs) hook runs when the native window state
changes. Call the base implementation to update the public IsMaximized value
before observing it in a derived control. IsMaximizedProperty is writable
through Avalonia properties, styles, and bindings despite its getter-only CLR
wrapper, matching the upstream dependency property's access. Setting that flag
alone does not resize the native window; use WindowState or window commands
to change the window state.
To enable arrow-key movement of floating windows, set the manager property:
<DockingManager AllowMovingFloatingWindowWithKeyboard="True">
<!-- LayoutRoot and application content -->
</DockingManager>
It defaults to false and can also use a compiled ViewModel binding or a style
setter. The Avalonia wrapper has a public setter, so a workspace service can use
manager.AllowMovingFloatingWindowWithKeyboard = true. This is a framework
adaptation: upstream's writable dependency property has a private CLR setter,
whereas Avalonia's compiled literal XAML calls that setter directly. WPF instead
writes dependency properties through its property system.
Unhandled arrow keys move the current floating document or tool window by 25
device-independent units, converted to desktop coordinates. Editors retain
their handled arrow keys, and window movement does not mark the key event
handled. Changes to the option take effect immediately for that manager's
existing hosts. Native position saving and XML persistence retain the moved
window's coordinates.
The default floating template supports ResizeBorderThickness, with a default
of 10 device-independent units on every edge. Add a style after AvalonDockTheme
in Application.Styles to configure all floating hosts:
<Style Selector=":is(LayoutFloatingWindowControl)" x:DataType="LayoutFloatingWindowControl">
<Setter Property="ResizeBorderThickness" Value="6,8,10,12" />
</Style>
Values are left, top, right, and bottom. Compiled style bindings and assignments
to an individual host update its hit areas at runtime. Eight transparent regions
provide edge/corner cursors, native resize roles, and Avalonia window resize
dragging. The template retains Dock's content presenter inside a border/inset so
ordinary tab and caption input stays clear of the resize areas. Grips are hidden
when CanResize is false or the host is maximized/fullscreen. Zero removes the
adapter's area for that edge; platform-owned system decorations are independent.
If you replace the floating window template, include your own resize hit areas.
Regression tests compare all 168 recorded WPF floating-close observations: 48 protected-hook cases and before/synchronous/dispatched states for 40 host scenarios. The public lifecycle deliberately includes these WPF behaviors:
| Operation | Before the queued close | After the queued close |
|---|---|---|
| Close document content | Content Closed has fired; the document still has its floating parent and original root |
Successful host closure detaches the floating model; cancellation preserves its root |
| Close the last tool content | Empty tool model detaches and its host hides | Native tracking is released on success; the host remains in the manager's public collection |
Replace Layout |
Old hosts leave the public manager collection immediately | Successful closure detaches old floating models; cancellation preserves them |
| Unload the view | Unload processing and host closure are deferred | Public host registrations clear even if closing is canceled; models remain for reattachment |
A closed document retains its public floating parent until another pane adopts
it. The public and native collection APIs both update that parent on adoption.
Reattachment creates hosts for preserved models, or registers surviving canceled
hosts again. A host retained in the public FloatingWindows collection may already
be closed; native tracking and command subscriptions are released on closure.
Additional regression tests cover explicit retries, native cleanup, fast
reattachment, and replacement hosts. Remaining platform hooks and behavior outside
these recorded scenarios stay open in the compatibility checklist.
Another 180 WPF observations cover direct child removal/replacement and model
subclasses suppressing property notifications. Document windows queue closure
from RootDocumentChanged when the document becomes null. Tool windows observe
RootPanel for closure and IsVisible for visibility. Suppressing a tool's
property notification affects its host; suppressing document property
notifications does not suppress the explicit document event. Clearing and
restoring a child before dispatch leaves an already queued close in place.
Floating models also match 300 recorded WPF observations of property-notification
order and callback-visible state. Document root changes raise PropertyChanging,
PropertyChanged, then RootDocumentChanged. Tool root changes notify
RootPanel, IsSinglePane, SinglePane, Children, and ChildrenCount;
visibility can change while the new group acquires its parent. A newly constructed
empty tool window starts visible until visibility is computed. Calling
ILayoutElementWithVisibility.ComputeVisibility() only notifies a changed
visibility value. Nested tree changes notify the single-pane properties separately.
IsSinglePane counts visible anchorable groups as well as panes below the root
panel. Reading SinglePane refreshes the returned pane's
IsDirectlyHostedInFloatingWindow notification; reading IsSinglePane does not.
The floating caption observes visibility independently so restoring a hidden
tool reconnects its selected-item binding without extra model notifications.
Reading empty floating XML preserves the existing child and window bounds. A
self-closing empty tool element recomputes visibility; an explicit empty opening
and closing pair leaves it unchanged, matching WPF. The single-child container
methods operate on the root slot: callers should pass the current child to
RemoveChild and ReplaceChild. As in the WPF Release build, a mismatched old
child does not prevent the operation, and an incompatible replacement clears
the slot. Native adoption checks actual membership so moving a retained former
child preserves the source window's replacement.
Removed or replaced root documents and tool root panels retain their public
floating parent until adopted by another container. Their native Dock owners
detach immediately, and both public and native collection insertion replace
the retained parent, including a move to another layout. Retaining a model
after its host closes does not retain that host through notification handlers.
The native HostWindow also detaches an Avalonia layout model's logical parent
before Closed callbacks, while preserving its Content value and the close
policy's retained layout. Cancellation leaves the parent intact. This native
lifetime fix is isolated in PR #1161.
The follow-up PR #1162 releases
nested model bindings and deferred visual content, including presenters from
replaced templates. The preserved layout can be shown in a replacement host
from Closed; these native links no longer retain the old host. Canceled closes
retain the current presentation. Both native fixes follow the separate-worktree/PR
workflow.
Public collection Clear()/Reset intentionally retains former Parent and
Root references, matching upstream. A retained model can consequently keep its
manager, layout-item Views, and their presentation graph reachable. To discard a
workspace, replace manager.Layout with a new LayoutRoot, which detaches the
old containers and Views. Closing its window or clearing FloatingWindows alone
does not dispose that public graph.
Inspect floating docking overlays
Enable Dock's floating adorner mode during application setup:
using Dock.Settings;
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseFloatingDockAdorner(true);
The manager and its floating hosts install an inherited
IDockAdornerWindowFactory through DockAdornerWindow.Factory. Native dragging
then creates an AvalonDock.Controls.OverlayWindow, a DockAdornerWindow
subclass. It hosts the same native DockTarget or GlobalDockTarget that Dock
uses for hit testing, capability validation, indicators, and drop execution.
The factory supplies the window host and its presentation of native operations.
Ordinary Dock without a factory keeps its standard window; inline and managed
adorners keep their existing layer paths.
The overlay receives the manager's resource theme, including live replacement
and dynamic resources. Its compiled control theme is based on the native adorner
window. It has no decorations, does not activate, and is absent from the taskbar.
The public OnApplyTemplate() hook connects the window's named parts to the
native target through IDockTargetPresentation. Templates without the overlay
canvas retain the hosted native target's template.
Both XML namespaces support the window type and target enum values in themes:
<ControlTheme x:Key="OverlayAppearance" TargetType="OverlayWindow"
x:DataType="OverlayWindow"
BasedOn="{StaticResource {x:Type OverlayWindow}}">
<Setter Property="Tag" Value="{x:Static DropTargetType.DocumentPaneDockInside}" />
</ControlTheme>
DropArea<T> implements IDropArea and retains the existing AreaElement, its
DropAreaType, and captured DetectionRect. OverlayArea and its manager,
document-pane, and tool-pane subclasses describe the corresponding surface.
OverlayWindowDropTarget describes an arranged native selector. The enum values
retain their upstream ordering, including document-group and dock-as-tool
destinations in DropTargetType.
Their constructors remain internal. The adapter adds public snapshot accessors
OverlayWindow.DropArea, Area, and DropTargets for inspection and custom
Avalonia presentation. Each read captures the current state; stored snapshots
do not update. DropTargets excludes selectors hidden by native docking policy.
Screen rectangles use physical pixels, matching Avalonia's PointToScreen;
use PointToClient when comparing with a control-local pointer position.
Transforms are included, and detached surfaces produce an empty rectangle.
Snapshot targets come from the active presentation or native template registrations used for hit testing.
DockTargetBase.GetSelector(DockOperation) returns that registered control,
including hidden selectors; it returns null for an absent operation. A custom
template's attached DockProperties.IndicatorDockOperation determines the
operation, independently of the part's name. Nested controls that merely reuse
a selector name are excluded. Replacing or clearing the template discards the
old selector and indicator registrations. Explicit native Template assignments
and custom DockTarget.TargetTheme values take precedence over the window's
default presentation. This preserves native customization when using an
AvalonDock overlay host.
The default window template includes AvalonDock's named selector groups and a single preview path. All template parts are Avalonia controls:
| Part | Purpose |
|---|---|
PART_DropTargetsContainer (Canvas) |
Holds the selector groups and preview in overlay coordinates. |
PART_DockingManagerDropTargets (Grid) |
Manager edge targets. |
PART_AnchorablePaneDropTargets (Grid) |
Tool pane edge and center targets. |
PART_DocumentPaneDropTargets (Grid) |
Document pane edge and center targets; groups expose the center only. |
PART_DocumentPaneFullDropTargets (Grid, optional) |
Document targets when outer tool destinations are available. |
PART_PreviewBox (Path) |
The active native preview rectangle. |
PART_NativeTargetPresenter (ContentPresenter) |
Hosts native Content and supplies the fallback presentation. |
Selector names use PART_DockingManagerDropTarget,
PART_AnchorablePaneDropTarget, PART_DocumentPaneDropTarget, or
PART_DocumentPaneFullDropTarget, followed by Left, Top, Right, Bottom,
or Into (no manager Into). Outer tool selectors are
PART_DocumentPaneDropTargetLeftAsAnchorablePane, and the corresponding Top,
Right, and Bottom names. Missing selectors omit those operations.
Dock still translates coordinates, checks capabilities and directions, validates
drops, and performs layout changes. The adapter positions groups over the
hovered area, updates selector opacity, and renders PART_PreviewBox. A custom
template can supply transformed controls; it does not need its own pointer
handlers. Keep the native presenter in custom Avalonia templates so native
geometry and extension properties retain an attached coordinate space. For
example, this template exposes only a document-center destination:
<ControlTheme x:Key="DocumentCenterOverlay" TargetType="OverlayWindow"
x:DataType="OverlayWindow"
BasedOn="{StaticResource {x:Type OverlayWindow}}">
<Setter Property="Template">
<ControlTemplate x:DataType="OverlayWindow">
<Grid>
<ContentPresenter x:Name="PART_NativeTargetPresenter"
Content="{TemplateBinding Content}" Opacity="0"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
<Canvas x:Name="PART_DropTargetsContainer">
<Path x:Name="PART_PreviewBox" Fill="DodgerBlue" Opacity="0.35" />
<Grid x:Name="PART_DocumentPaneDropTargets">
<Border x:Name="PART_DocumentPaneDropTargetInto"
Width="40" Height="40" Background="DodgerBlue" />
</Grid>
</Canvas>
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
Canceling window closure preserves its projection. Completed closure clears the native content, external presentation, and projection references and releases theme providers. Retaining the optional factory does not keep its manager alive.
Dropping documents or tools on a document pane's split targets creates another
LayoutDocumentPane inside a LayoutDocumentPaneGroup. Mixed orientation creates
a nested document group; an existing group with the matching axis is reused.
A tool-pane drop keeps its tab order and existing layout item containers. Later
rendering may bring an overflowing selected tab to the front, as in WPF.
The compatibility factory supplies Dock's optional IDockSplitPolicy to select
the document destination; Dock creates the pane and moves the content. Ordinary
Dock factories keep their tool-pane split default.
The four DocumentPaneDockAsAnchorable* targets place tools beside the highest
containing document-pane group. They remain available when
CanDockAsTabbedDocument is false. Dropping a pane or group preserves that
container, its children and tab order; single tools get a new anchorable pane.
With AllowMixedOrientation=False, a group with multiple visible children
exposes only its outside edges along the existing axis. True enables all four.
The compatibility factory implements the optional IDockOuterSplitPolicy;
Dock validates capabilities and moves the native models. The adapter supplies
four extra selectors through DockTarget.TargetTheme, scoped to its surfaces.
They use native OuterLeft, OuterTop, OuterRight and OuterBottom operations
and the existing directional restrictions. The public overlay projection reports
the corresponding DropTargetType.DocumentPaneDockAsAnchorable* values.
Outer highlights span one third of the highest containing LayoutDocumentPaneGroup,
or the parent LayoutPanel when the pane has no document group. Ordinary document
splits still highlight half of the hovered pane, and a center drop highlights the
whole pane. Selectors remain centered over the hovered pane in all three cases.
The adapter supplies an inherited DockTarget.AdornerHostProvider. Its
IDockAdornerHostProvider resolves the enclosing control without changing the
docking destination. Dock updates the adorner bounds and read-only TargetMargin
when the layout or window changes; the target theme applies that inset to its
local selectors and indicators. Inline, native-window, and managed-window hosts
support the same geometry, including scaled layouts. Removal or surface detachment
releases geometry subscriptions and clears the inset. The floating overlay's
named parts use the same native operations through the optional presentation
contract added in PR #1167.
Apply AvalonDock resource themes
DockingManager.Theme accepts AvalonDock.Themes.Theme. The default is null,
which uses the resources installed by AvalonDockTheme. Select the compiled
standard resource bundle with unprefixed XAML:
<DockingManager xmlns="https://github.com/avaloniaui">
<DockingManager.Theme>
<GenericTheme />
</DockingManager.Theme>
</DockingManager>
The protected OnThemeChanged hook uses its event argument's new value for
manager resources. Auxiliary windows read the manager's current Theme
property. If an override redirects the property before calling the base hook,
the manager can therefore display the originally requested theme while its
windows display the redirected theme. Suppressing or throwing before the base
call leaves existing resources intact; a newly created floating host, navigator,
or overlay still reads the current property. Replaying a captured base callback
applies its argument without changing the property. Resource reentrancy, close
cancellation, and closed-window cleanup retain their normal behavior.
For custom resources, derive from DictionaryTheme and supply an Avalonia
ResourceDictionary:
using Avalonia.Controls;
using Avalonia.Media;
using AvalonDock.Themes;
public sealed class EditorTheme : DictionaryTheme
{
public EditorTheme() : base(new ResourceDictionary
{
["DockThemeBackgroundBrush"] = new SolidColorBrush(Color.Parse("#253345"))
}) { }
}
// In the application's docking composition code:
manager.Theme = new EditorTheme();
A dictionary can be shared by multiple managers, including through separate
DictionaryTheme instances. The adapter supplies each manager, floating host,
navigator, and overlay with its own resource provider. The providers share values and
forward resource notifications; they do not copy the dictionary or attach it
to several Avalonia owners. Key and theme-variant changes update dynamic resources.
An existing external dictionary owner is preserved, including transfers between
owners. Resources remain available after hiding a navigator or canceling a close;
completed window closure removes that window's provider. Clearing or replacing the
manager's theme updates existing windows, and subsequently created windows receive
the current theme. Unrelated local resources and merged dictionaries remain intact.
A resource notification or GetResourceUri() override can close a window during
theme replacement. Successful closure stops that replacement and releases the
window's resources. A canceled close keeps the replacement theme active and
allows later resource updates.
ThemeProperty is an Avalonia styled property, and OnThemeChanged is a protected
virtual hook using AvaloniaPropertyChangedEventArgs. Both XML namespaces support
compiled theme bindings. The AvalonDock property hides Avalonia's inherited
StyledElement.Theme; use ((StyledElement)manager).Theme in C# when assigning an
Avalonia ControlTheme to the manager itself.
GenericTheme.GetResourceUri() identifies the compiled Themes/Generic.axaml
dictionary. Applications can also include it directly in their merged resources:
<ResourceInclude Source="avares://Dock.Avalonia.AvalonDock/Themes/Generic.axaml" />
The bundle includes Dock's base floating-window resources, so it also works when
AvalonDockTheme is installed in a view's styles instead of application styles.
The historical framework fork supports generated resource factories (this API
is not available in official Avalonia). On that fork, the package's opt-in
incremental generator discovers compiled ResourceDictionary AvaloniaResource items in the application and referenced libraries. A partial Application
class receives an instance catalog; custom theme URIs matching an avares://
resource load through direct compiled factory calls. Both classless dictionaries
and dictionaries with x:Class are supported. No manual registration is needed
for these resources.
An explicit per-manager factory overrides the generated catalog. For example,
an application's compiled dictionary with x:Class="Editor.EditorThemeResources"
can be registered under an application-selected URI:
Uri themeUri = new("avares://Editor/Themes/EditorThemeResources.axaml");
manager.ThemeResources.Register(themeUri, static () => new EditorThemeResources());
manager.Theme = new EditorTheme(themeUri);
sealed class EditorTheme(Uri uri) : AvalonDock.Themes.Theme
{
public override Uri GetResourceUri() => uri;
}
The dictionary's constructor loads its compiled XAML using the usual
AvaloniaXamlLoader.Load(this) pattern. Register before assigning the theme.
Each manager has its own registry, which is also used by floating hosts,
navigators, and overlays. Factories can create independent dictionaries or
return an existing shared dictionary; dynamic resource updates and ownership
follow the same rules as DictionaryTheme. URI keys may be absolute or relative
because lookup uses the supplied factory, without reading URI content.
Unknown URIs fail with NotSupportedException; null factory results and cyclic
resource ownership are rejected. The built-in generic URI is reserved.
DictionaryTheme.GetResourceUri() returns null because it supplies its dictionary
directly. There is no runtime assembly scan or reflection-based XAML loader.
A DictionaryTheme constructed without a dictionary cannot be applied, and a
non-dictionary theme must return a URI. Applying either invalid configuration
fails after removing the manager's previous theme resources, following the
upstream ordering and WPF's rejection of null merged dictionaries
and null resource URIs.
Navigate documents and tools with Ctrl+Tab
Ctrl+Tab opens an AvalonDock.Controls.NavigatorWindow from docked content or
either floating host type. The window uses the manager's live LayoutDocumentItem
and LayoutAnchorableItem containers. Enabled documents are captured in descending
LastActivationTimeStamp order, with layout order preserved for ties; tools must
be visible and enabled and retain layout order. The lists are snapshots, while
the containers' titles, icons, and activation commands remain live.
The initial selection is the second document when there are at least two;
otherwise it is the second tool when available. With smaller lists, neither
item is initially selected. Up/Down wrap within the selected list, Left/Right
cross to the other list at the same or nearest index, and Tab traverses both
lists. These follow the pinned upstream behavior: Shift does not reverse Tab,
and releasing any non-navigation key, including Control or Escape, closes the
navigator and invokes the selected item's available ActivateCommand with null.
For inline Avalonia controls, focus is restored after Dock's queued deferred
presentation, either to the content itself or its next focusable child.
This also works with delayed timelines and a presentation budget, in docked and
floating documents. The request waits for visual attachment and ends when content
changes or deactivates, the manager detaches, or the user focuses another control
in the destination window. Floating selection uses Dock's host activation API
after the modal navigator closes. Retaining unrealized content does not retain
its closed manager through the pending request.
Pointer or application selection of SelectedDocument hides the navigator and
executes its available command; selecting SelectedAnchorable closes it and
executes the tool command. Null or disabled commands prevent these immediate
actions. Keyboard release still closes the navigator when activation is unavailable.
Override CreateNavigatorWindow() to configure the window before the manager
shows it:
using AvalonDock;
using AvalonDock.Controls;
public sealed class EditorDockingManager : DockingManager
{
public override NavigatorWindow CreateNavigatorWindow() => new(this)
{
LayoutDocumentsLabel = "Documents",
LayoutAnchorablesLabel = "Tools"
};
}
The factory creates an unshown window. Ctrl+Tab uses the protected
ShowNavigatorWindow() hook to present one modal navigator, centered over the
manager's window. If the manager has no visible owner window, Avalonia presents
the navigator without an owner. Closing or hiding permits the next request to
create a fresh snapshot. Layout replacement and view detachment close the current
navigator; a canceled close retains the current window until closure succeeds.
Avalonia clears Window.Owner when a window is hidden. The adapter separately
tracks the former owner's lifetime so document selection can keep the upstream
hide behavior without leaving a hidden window in the desktop application's
window list after its owner closes. Canceling the owner's close keeps the hidden
navigator available. After the owner has closed, the adapter closes the hidden
navigator programmatically and disregards cancellation from its Closing event
handlers. Showing the same navigator again switches tracking to its new owner,
or clears tracking when shown without one; explicitly closing it releases tracking.
The package consumer verifies that multiple hidden navigators do not prevent
ShutdownMode.OnLastWindowClose from shutting down a desktop lifetime.
LayoutDocumentsLabel and LayoutAnchorablesLabel also support unprefixed XAML
styles with x:DataType="NavigatorWindow". A custom control template can bind
Documents, Anchorables, and the selected item properties; the default template
uses compiled bindings and the parts PART_DocumentListBox and
PART_AnchorableListBox. The public parameterless OnApplyTemplate() hook is
called by Avalonia template application. Derived navigators can replace the lists
through protected SetDocuments/SetAnchorables methods and override selection
and preview-key hooks using Avalonia event arguments.
Broader desktop modality/focus verification remains open work. The native Dock selector continues to handle its other gestures.
Restore application content from XML
The XML describes layout structure and metadata; it does not serialize controls
or arbitrary ViewModels. A newly constructed XmlLayoutSerializer captures
the existing document and tool models separately. Restoration matches their
current ContentId values, using the first matching model of the same kind.
Construct it after the relevant content exists; later additions are not part
of that serializer's snapshot. Changed identifiers and content remain visible
through the captured model references.
For a new application session, resolve IDs using a content registry:
XmlLayoutSerializer serializer = new(manager);
serializer.LayoutSerializationCallback += (_, args) =>
{
if (contentById.TryGetValue(args.Model.ContentId, out object? content))
args.Content = content;
else
args.Cancel = true;
};
serializer.Deserialize("layout.xml");
contentById is an application-owned dictionary of stable identifiers to
ViewModels. Set Cancel to request closure of an unavailable item. The content's
Closing event can veto that request, and successful closure raises Closed.
Restoration occurs before the new layout has a manager; CanClose does not
suppress this request. Source collection
notifications are suspended while callbacks run, then resume even if loading
fails. Parsing and content resolution finish before replacing the manager's
layout. The runnable sample writes to a temporary file and replaces the saved
file after a successful save.
Nested restores on the same manager keep source notifications suspended until
the outer restore exits, including when an inner callback throws. Assigning a
new DocumentsSource or AnchorablesSource inside a callback immediately
replaces that source; later collection notifications come from the replacement.
Source assignment and layouts committed by callbacks are application side
effects and survive a later callback exception. In particular, LayoutChanged
runs after replacement: an exception from that event does not restore the old
layout. A nested restore from that event leaves its own layout installed.
Only models with null Content enter content recovery. Tool callbacks run
before the document phase, so they may supply document content before that
phase takes its snapshot. The callback and fallback cases follow upstream:
| Recovery case | Result |
|---|---|
| No callback, matching previous model | Copy its current content and icon, including null values. |
| No callback, no previous model | Hide an unresolved tool; close an unresolved document. |
Callback sets Cancel = true |
Request content closure, preserving closing-event cancellation. |
Callback supplies non-null args.Content |
Assign it; the callback owns icon restoration. |
Callback leaves both args.Content and args.Model.Content null |
Keep an unresolved placeholder in its current location. |
Callback assigns args.Model.Content but leaves args.Content null |
Hide that tool or close that document. |
Set args.Content to provide a view or ViewModel. A callback can set
args.Model.IconSource explicitly; callback-driven restoration does not copy
the previous icon over it. Already-populated models retain their content and
icon without receiving a callback.
Docked, hidden, auto-hide, and supported floating layouts retain return
locations and floating geometry. Native grouped documents, generic panels, and
mixed document/tool panels use LayoutGroupFloatingWindow, with their original
panes under RootPanels. This is an additional Dock model and XML element, not
part of the upstream WPF schema. Original single-document and tool-only window
models continue to serialize with their original element names. Floating native
roots with hidden/pinned contents or nested window collections remain rejected
before an existing save file is overwritten.
Individual content, pane/group, root, and floating models also implement
IXmlSerializable. Their WriteXml methods write attributes and children inside
an element opened by the caller. Content reads preserve an existing Content
object, group reads append children, and root reads replace the hierarchy while
retaining the native root and factory.
These methods do not resolve application content or return-container references;
use XmlLayoutSerializer for complete layout restoration and content callbacks.
Content ReadXml uses the ordinary setters in AvalonDock's order: document/tool
attributes first, then shared metadata, with selection immediately after the title
and before ContentId. A later conversion or application callback failure leaves
preceding assignments intact and the reader on the failing attribute. Content
reads advance the caller's reader once instead of buffering the element, so a
derived override can read its own child elements after base.ReadXml(reader).
Explicit start/end tags leave the reader on the end tag; self-closing tags advance
to the following node. Groups, roots, and floating windows also use the original
reader: registered hooks retain the document depth, line information, namespaces,
and following siblings. Group metadata errors leave the reader on the failing
attribute. Completed children remain attached if a later child or XML syntax fails.
Unknown child names raise ArgumentException during type lookup.
Direct root reads apply sections incrementally. The root panel's children are read before replacing the panel, then attach after the replacement notification. Sides are replaced before their children are read; floating and hidden collections are cleared before reading their replacements. Children of those sections attach only after the section succeeds. A later failure retains those preceding changes. Compact layouts may omit empty sections, and Dock's additional root-panel metadata continues to round-trip. Self-closing root reads retain the existing layout.
Floating reads replace their root child for each successfully read child, keeping the last replacement and its restored bounds. Later failures retain the preceding replacement. Empty reads preserve existing children and geometry. The caller owns the surrounding element name, including for standalone floating-window reads.
XmlLayoutSerializer.Deserialize keeps the manager's existing layout until parsing
and content recovery succeed. It consumes one layout and leaves following XML
available to the caller. Parse errors and errors from model ReadXml hooks become
InvalidOperationException with the original cause and reader location. Nested
model reads add nested wrappers, following the
.NET XML serializer boundary.
Direct model metadata errors remain unwrapped. Content-recovery callbacks, return
reference resolution, and manager layout replacement happen after parsing and
retain their original errors.
Custom child payloads require a registered ReadXml override that consumes them.
An unregistered subclass's extra attributes can be read by the built-in model,
but arbitrary child XML, including comments appended inside content elements,
is not silently discarded by streaming readers.
IsLastFocusedDocument restores both true and false without changing root focus
references, IsActive, or the saved activation time. IsFloating also restores
both values as metadata without moving content or opening a window, and true
values are written back to XML. Subsequent native docking-state changes update
the floating flag. Full layout restoration continues to build native placement
from the layout hierarchy; a persisted focus flag does not activate its content.
Generic layout groups expose container/group APIs without automatically
implementing ILayoutPane, ILayoutPanelElement, or ILayoutElementWithVisibility.
Concrete document/tool panes and pane groups supply their pane interfaces;
LayoutPanel supplies panel-element and visibility interfaces. Auto-hide sides
and anchor groups retain their public visibility methods without becoming panes
or panel elements. Self-closing group XML invokes the class's visibility method,
so an additional interface on an application subclass does not replace that call.
LayoutDocumentPane and LayoutAnchorablePaneGroup recompute parent visibility
before their inherited visibility hook, which recomputes the current parent again.
A reentrant parent change redirects the second pass. Suppressing the base hook
skips both passes; a failure stops the remaining callbacks.
Group readers follow their class inheritance: concrete panes and orientable
groups restore their own metadata, LayoutPositionableGroup<T> restores dimensions,
and LayoutGroup<T> reads children. Adding an interface to an application subclass
does not make the base reader process that interface's attributes. Registered
custom overrides can call the appropriate base reader and retain the live reader
context. Suppressing a base call also suppresses that level's metadata and children.
Application classes may derive from LayoutGroupBase through its protected
parameterless constructor. Each instance receives its own native Dock container;
parent/root identity and protected collection/tree notification hooks remain usable.
Collection replacement follows ObservableCollection semantics even when the
new entry is the same instance: parent hooks detach and reattach it, selection
hooks run, and subscribers receive Replace. Incoming parent callbacks see the
item already in its target collection. Detachment callbacks may move an item to
another parent; reattachment reconciles that membership before publishing the
replacement. Hidden and pinned tool replacements retain root activation.
Nested count and parent callbacks can insert siblings while retaining each item's
public and native ownership.
Replacing a floating window with itself preserves its native host and registration
while publishing the parent transitions and collection notification.
Replacing it with another model closes and unregisters the old native host, then
publishes one Replace notification after the parent hooks. Those hooks see the
incoming model in the collection. Native close cancellation retains the original
entry; windows added by a closing callback still appear. If a public callback
throws, completed collection and parent changes remain, and assigning the same
entry again can finish attachment and presentation. A retained closed host stays
closed when its model is replaced with itself.
Group ReadXml restores stored docked/floating dimensions, IDs, tool-pane
names, and document header flags without public setter notifications or pixel-size
propagation. Native minimum values still update the existing Dock model. Public
changes made by native observers, and later ordinary edits, retain notifications.
Attributes apply in inheritance order; a later conversion error leaves earlier
assignments in place. XML minima restore stored values even when negative, while
ordinary minimum setters reject changed negative values. Orientation accepts
case-insensitive names and numeric enum values. Self-closing empty groups recompute
visibility; explicit empty start/end pairs retain the current visibility.
The RootPanel section uses its own orientation rule: only the exact value
Vertical selects vertical layout; missing, differently cased, numeric, or invalid
values select horizontal layout. Standalone/nested LayoutPanel.ReadXml still
uses the enum parser, even when the caller names its element RootPanel.
Root panels and root side sections consume recognized children of other model
kinds without attaching them. Registered read hooks still run for those children,
but discarded content does not enter content recovery. Unknown child names still
fail, preserving the section's existing staging and partial-state rules.
Native root sizing/options and compact roots remain adapter XML extensions.
XML double attributes use invariant CLR number spellings, including Infinity,
-Infinity, and NaN, so the values accepted by the readers can be saved and read
again. Previous and initial container indices use the current culture for both
writing and reading, matching AvalonDock. Invalid standalone/nested group orientation text raises enum
parsing errors (ArgumentException or OverflowException) before later metadata
is applied.
Tool panes defer automatic fallback selection until XML restoration succeeds.
An explicit selected child or an application callback can still select immediately.
A failed metadata conversion or child callback leaves fallback selection disabled;
a later successful ReadXml reenables it. A successful nested read also reenables
it during the outer read. These rules apply to built-in panes and registered
subclasses, preserving the selection observed by child-collection hooks.
LayoutAnchorablePane.Name is layout metadata, separate from the native model's
Avalonia styling name. It remains editable after native styling and raises a
changed notification when its value changes. XML restoration writes it silently.
Concrete models declare their own WriteXml overrides. Tool-specific attributes
precede common content metadata; document-specific attributes follow it. Pane
IDs, names, and header flags precede dimensions, while orientable groups write
their orientation first. Generic base writers do not infer concrete attributes
from extra interfaces on application subclasses. Writer callbacks observe this
order, and a failure stops later attributes and hooks.
Serialize(XmlWriter) passes the live caller context to model hooks: namespace
prefixes, xml:lang, xml:space, settings, and output order remain available.
Serialized child elements use an empty namespace, independent of a containing
application element. Caller-owned output stays open. Text/stream overloads also
leave a retained hook writer usable after returning; failures do not complete
partial XML. Byte output uses UTF-8 without a byte order mark, while text output
uses the caller's encoding in its declaration.
Model and writer errors preserve nested InvalidOperationException wrappers at
the same serializer boundaries as group and floating child writes. Direct root
sections and hidden tools add no child serializer boundary. Each completed child
serializer flushes; its flush is outside its own error wrapper and inside any
parent serializer. The outermost flush error stays unchanged. See the
.NET writer contract.
The file overload continues to stage output before opening its destination, so
model failures preserve an existing file; that is an intentional Dock extension.
Saved booleans use True and False. CanClose is always written because tools
and documents have different defaults. Default star lengths, 25-unit group
minima, zero floating dimensions, and default flags are omitted. Blank titles,
content IDs, descriptions, and string tooltips are omitted without modifying the
model; use nonblank content IDs for automatic recovery after a save. Explicit
empty pane IDs and pane names remain supported. Auto-hide sizes are written only
when positive. Following upstream's write condition, auto-hide minima of 25 are
omitted even though newly created tools start at 100 and write that value.
Saving does not assign IDs to unused containers. Existing IDs and explicit empty
IDs remain, and referenced native groups receive IDs for return-location recovery.
Duplicate native IDs are still made unique, preserving pane precedence. Return
references to roots, floating windows, or anchor sides are omitted because those
containers have no serializable pane identity. Native group IDs and nondefault
AllowDuplicateContent / CanRepositionItems remain Dock XML extensions.
Whole-layout writes dispatch each nested model's virtual WriteXml, including
panes, groups, side/anchor nodes, hidden content, and floating models. Custom
overrides can add attributes before calling base; content-only models may also
add attributes after base. For groups, base writes children, so write attributes
first. The traversal shares one XML writer and prepares return-container IDs
once. A failing override is detected before a file overload opens its destination.
With <AvalonDockGenerateCatalog>true</AvalonDockGenerateCatalog>, the package's
incremental generator discovers constructible custom layout classes during compilation. The application must be a non-nested, non-generic
partial Application class. Its instance catalog composes factories emitted by
referenced libraries, preserving custom document, tool, group, and floating-window
types and their XML hooks without runtime assembly discovery. Concrete classes
must implement IXmlSerializable and need an accessible, non-file-local declaration
and a public parameterless constructor. Reserved built-in XML names are skipped
with a diagnostic and require an explicit unique name. Ambiguous names are build
errors: rename the types, or set
<AvalonDockGenerateCatalog>false</AvalonDockGenerateCatalog> in the project and
use explicit layout/resource registrations. The opt-out preserves any manual
application catalog provider.
Explicit registrations override generated names or factories and support models requiring application-specific construction:
var serializer = new AvalonDock.Layout.Serialization.XmlLayoutSerializer(manager);
serializer.KnownTypes.Register("EditorDocument", static () => new EditorDocument());
serializer.KnownTypes.Register("EditorPane", static () => new EditorPane());
serializer.Serialize(path);
serializer.Deserialize(path);
EditorDocument and EditorPane derive from the corresponding public layout
models. Their virtual WriteXml and ReadXml methods can write and read additional
attributes around their base call. Registrations also work for hidden tools and
floating-window subclasses. Content recovery, selection, focus metadata, and
return locations use the same restoration pipeline as built-in types.
Register each XML name and exact CLR type once on both saving and restoring serializers. Factories must return a fresh detached instance; null, attached, or differently typed results are rejected. Built-in XML element names are reserved. Operations snapshot the registrations so callbacks can configure a subsequent operation without changing the current traversal. Factory or reader failures leave the current manager layout in place.
Subclasses absent from both the generated catalog and explicit registry retain
the built-in element name when saved. Unknown input elements fail. This typed factory
path keeps custom type restoration reflection-free; direct standalone ReadXml
continues to operate on the instance supplied by the application.
Implementation architecture
The shared Dock prerequisites are maintained in the native packages and were
merged through PR #1160.
The facade consumes those extension points after merging master; the native
projects have no dependency on AvalonDock. See the
native extension guide
for policy defaults, sizing and host contracts, examples, and regression coverage.
PR #1184 adds the nullable
PinnedDockControl.UsePinnedDockWindow override. The adapter scopes it to false
so the existing preview presenter can move into its embedded native root without
changing the application's global host policy.
PR #1185 extends registered
external surfaces to route dragging, docking targets, and previews across native
roots, including display-density conversions for floating geometry.
The branch also includes the native drag and preview fixes from PR #1153 and PR #1156: adorner reuse, interrupted-drag cleanup, selector coordinates, Escape cancellation, tab previews, global split proportions, and display-scaling fixes. PR #1187 adds a source-preview scope for custom layouts. The adapter uses it to reflow its public grids without mutating layout models. Exact projected destination layout previews remain disabled for the compatibility factory and its custom sizing panel. The existing AvalonDock indicators retain their documented geometry; an isolated factory alone cannot reproduce its layout policies and sizing. See the compatibility checklist for the integration analysis and remaining requirements.
At build time, the incremental generator discovers application layout types and
resource paths and composes typed catalogs from referenced assemblies. Avalonia's
XAML compiler replaces resource-factory stubs with compiled constructors. At
runtime, the application exposes that immutable catalog through
IAvalonDockGeneratedCatalogProvider; XML operation snapshots and theme lookup
consume its typed factories. Explicit registrations take precedence. The adapter
opts into framework binding and logical-lifetime semantics and uses framework
hooks for embedded roots, initialization, and input. Native Dock still owns the
layout and docking operations.
flowchart TB
App["Application XAML, ViewModels, and workspace service"]
Generator["Incremental catalog generator<br/>Application types, resource paths, and referenced catalogs"]
Catalog["Per-Application catalog provider<br/>Typed local and referenced-assembly catalogs"]
XamlCompiler["Avalonia XAML compiler hook<br/>CompiledResourceFactory stubs to typed resource factories"]
Framework["Pinned Avalonia opt-in APIs<br/>Binding assignment; logical source and weak inheritance<br/>Embedded roots, deferred initialization, and mouse capture"]
Explicit["Explicit layout and theme registries<br/>Override generated registrations"]
App -->|Compile-time inputs| Generator
Generator -->|Typed constructors and catalog composition| Catalog
Generator -->|Resource factory stubs| XamlCompiler
XamlCompiler -->|Compiled resource constructors| Catalog
App -->|Generated catalog-provider interface| Catalog
Catalog -->|Operation snapshot| Xml
Catalog -->|Typed URI factories| ResourceThemes
Explicit --> Xml
Explicit --> ResourceThemes
Manager --> Framework
Items --> Framework
AutoHide --> Framework
Hosts --> Framework
subgraph Compatibility["Dock.Avalonia.AvalonDock"]
Manager["DockingManager + AvalonDockTheme"]
Layout["AvalonDock.Layout API"]
ModelOperations["Explicit model operation contracts<br/>Close, auto-hide, document placement, and float history"]
Items["LayoutItem containers<br/>Content, headers, window titles, styles, and commands"]
GridControls["Public grids<br/>Direct panes, splitters, rows and columns"]
PaneControls["Public pane controls<br/>Live model, selection, and manager ControlThemes"]
Anchors["Public auto-hide side, group, and anchor controls<br/>Manager templates and preview gestures"]
AutoHide["LayoutAutoHideWindowControl and LayoutGridResizerControl<br/>Preview, constrained resizing, and idle closing"]
ContentControls["LayoutDocumentControl / LayoutAnchorableControl<br/>Model, LayoutItem, and existing View"]
TabPanels["DocumentPaneTabPanel / AnchorablePaneTabPanel<br/>Natural widths, compression, and overflow selection"]
Tabs["LayoutDocumentTabItem / LayoutAnchorableTabItem<br/>Existing items, header templates, and custom row input"]
ToolTitle["AnchorablePaneTitle<br/>Current item, title templates, and custom grip input"]
Commands["Compiled command bindings<br/>Native input routing"]
Converters["AvalonDock.Converters<br/>Public models to current commands"]
Navigator["NavigatorWindow<br/>Snapshot lists and keyboard selection"]
ResourceThemes["Themes.Theme<br/>Shared dictionaries and typed compiled URI factories"]
Overlays["OverlayWindow named parts and geometry snapshots"]
Menus["Document and tool context menus<br/>LayoutItem binding context"]
MenuControls["DropDownButton / DropDownControlArea<br/>ContextMenuEx / MenuItemEx"]
DocumentList["Document list menu<br/>LayoutContent headers and icons"]
Bridge["Identity, collections, selection, and return history"]
Selection["Pane selection and removal fallback<br/>Native factory focus policies"]
Focus["Active content and document history<br/>Across native roots"]
ContentHooks["Content state and close overrides"]
TreeEvents["Parent and root hooks<br/>Layout membership and Updated events"]
Structure["Internal structure notifications<br/>Command refresh and item cleanup"]
Sizing["Pane dimensions and splitter settings<br/>Grid definitions and native proportions"]
Lifecycle["Floating hosts, templates, and close/hide lifecycle"]
Events["Operation notifications<br/>Public layout source and routed events"]
Hosts["Floating control API<br/>Typed and grouped hosts, deferred creation, and notifications"]
Xml["XmlLayoutSerializer + explicit XML codec<br/>Live readers and writers, typed custom factories, virtual hooks, and content recovery"]
end
subgraph Native["Existing Dock implementation"]
Models["Dock.Model.Avalonia models"]
Factory["Dock factory and docking operations"]
Controls["DockControl, HostWindow, and Fluent controls"]
OverlayFactory["Optional IDockAdornerWindowFactory"]
TargetPresentation["Optional IDockTargetPresentation<br/>External selectors and preview state"]
DragPolicy["Optional IDockItemDragPolicy<br/>Native tab drag and reorder checks"]
DragPreview["Full-content drag previews<br/>IDockDragPreviewPolicy and content snapshots"]
SourcePreview["Drag source lifetime<br/>IDockDragSourcePreviewProvider"]
DropPolicy["Optional IDockDropPolicy<br/>Target visibility and drop validation"]
SplitPolicy["Optional IDockSplitPolicy<br/>Tool drops into typed document groups"]
end
Manager -->|LayoutRootPanel| GridControls
GridControls --> Sizing
GridControls --> PaneControls
GridControls -->|DockableControlTracker and IProportionalSplitterHost| Controls
GridControls --> Controls
Bridge --> Structure
Structure --> Items
Manager --> PaneControls
Manager -->|ShowDockablePreviewOnDrag| DragPreview
Controls --> DragPreview
Controls --> SourcePreview
SourcePreview -->|Temporary grid visibility and sizes| GridControls
SourcePreview -->|Dispose on cancel or before drop| Sizing
Controls --> OverlayFactory
OverlayFactory --> Overlays
ResourceThemes --> Overlays
Factory --> SplitPolicy
SplitPolicy --> Models
Factory --> OuterSplitPolicy["Optional IDockOuterSplitPolicy<br/>Tool panes and groups beside document regions"]
OuterSplitPolicy --> Models
Controls --> PreviewHost["Optional IDockAdornerHostProvider<br/>Enclosing document region and local selector inset"]
PreviewHost --> PreviewTarget["DockTarget geometry<br/>Inline, native-window and managed layers"]
PreviewTarget --> Overlays
Overlays --> TargetPresentation
TargetPresentation -->|Native hit testing and drop validation| Controls
Manager -->|Side panels and anchor templates| Anchors
Anchors --> Models
Anchors -->|Pinned reorder and preview| Factory
Manager -->|AutoHideWindow and closing delay| AutoHide
PinnedWindows["Native PinnedDockWindow binding<br/>Single editor host, themes, and input"]
AutoHide --> PinnedWindows
ResourceThemes --> PinnedWindows
PinnedWindows --> ContentControls
AutoHide --> ContentControls
AutoHide -->|Observe preview, commit pinned bounds, and close through Dock| Factory
Controls --> Anchors
PaneControls --> ContentControls
PaneControls --> Tabs
PaneControls --> Selection
App --> Manager
App --> Converters
ResourceCultures["21 command resource keys<br/>English and 13 satellite cultures"] --> Converters
ResourceCultures --> ResourceThemes
Converters --> Items
Manager --> ResourceThemes
ResourceThemes --> Navigator
ResourceThemes --> Hosts
ResourceThemes --> Controls
Manager --> Navigator
Navigator --> Items
App --> Layout
App --> Xml
Manager --> Layout
Manager --> Registration["Container registration<br/>After source callbacks; eager layout attachment"]
Registration --> Items
Items <--> Layout
Items -->|Capability and last-child checks| Factory
Layout --> ModelOperations
ModelOperations -->|Shared native lifecycle| Factory
Items --> Controls
Controls --> ContentControls
ContentControls --> Items
ContentControls --> Layout
Controls --> TabPanels
TabPanels --> Layout
TabPanels --> Controls
Controls --> Tabs
Tabs --> Items
Tabs --> Layout
Tabs --> Factory
Controls --> DragPolicy
DragPolicy --> Factory
Controls --> DropPolicy
DropPolicy --> Layout
DropPolicy --> Factory
Controls --> ToolTitle
ToolTitle --> Items
ToolTitle --> Factory
ToolTitle --> Controls
Controls --> Commands
Commands --> Items
Manager --> Menus
Controls --> Menus
Menus --> Items
App --> MenuControls
MenuControls --> Menus
Manager --> DocumentList
Controls --> DocumentList
DocumentList --> Layout
DocumentList --> Items
DocumentList -->|MenuItemEx containers| MenuControls
Manager --> Controls
Layout <--> Bridge
Bridge <--> Models
Layout --> Selection
Selection --> Models
Selection --> Factory
Manager --> Sizing
Sizing --> Models
Sizing --> Controls
Layout --> Lifecycle
Lifecycle --> Factory
Lifecycle --> Controls
Controls --> Factory
Factory --> Models
Factory --> Focus
Focus --> Layout
Focus --> ContentHooks
Factory --> ContentHooks
ContentHooks --> Layout
Models --> TreeEvents
TreeEvents --> Layout
Factory --> Events
Events --> Manager
Factory --> Hosts
Hosts --> Controls
Hosts --> Manager
Hosts --> Items
Hosts --> Caption["Platform caption and closing hooks"]
Layout --> ModelEvents["RootDocumentChanged / RootPanel / IsVisible<br/>Deferred closure and visibility notifications"]
ModelEvents --> Hosts
Factory --> ClosePolicy["Optional IDockWindowClosePolicy<br/>Preserve layout when closing a compatible host"]
Hosts --> ClosePolicy
Hosts --> CloseCore["HostWindow.OnClosedCore<br/>Captured-root collection before Closed; original factory retained"]
CloseCore --> PresentationCleanup["Native model and deferred-presentation cleanup<br/>Before Closed; retained layout can reopen"]
Caption --> SystemMenu["Windows system-menu service<br/>Source-generated interop"]
SystemMenu -->|Native window commands| Hosts
Models --> Controls
Xml <--> Layout
The facade wraps the same native objects that Dock renders and moves.
LayoutModelContext preserves wrapper identity, and LayoutChildren projects
native child lists as observable AvalonDock collections. Native changes flow
back into public ownership, selection, and active-content state. Compatibility
metadata, such as AvalonDock return-container references, remains in the adapter.
For native split groups, the containing public group determines whether the
new ProportionalDock is projected as a document or tool group. This retains
the typed hierarchy even while native collection notifications precede owner
assignment. LayoutFactory composes LayoutDropPolicy through the optional
native IDockDropPolicy interface. DockManager checks source and destination
policies before exposing targets or executing a drop; ordinary factories retain
their existing behavior. The adapter supplies floating-destination and
mixed-orientation rules while Dock performs the accepted operation.
LayoutPaneSelection composes the document/tool selection rules and notifications.
The native factory's protected ShouldFocusActiveDockable policy lets the adapter
select a tab without automatic activation while preserving Dock's selection
synchronization. GetActiveDockableAfterRemoval supplies the pane's replacement
after the native child collection has finished notifying. Default Dock behavior
is unchanged. During native collection notifications, the adapter defers selection
repair so item generators can finish before presentation changes. Factory
completion or the next public read reconciles the state; a queued fallback uses a
weak reference so it cannot retain a removed pane.
| AvalonDock API | Native Dock implementation |
|---|---|
LayoutRoot |
RootDock and its factory |
LayoutPanel, document/tool pane groups |
ProportionalDock |
LayoutDocumentPane |
DocumentDock |
LayoutAnchorablePane |
ToolDock |
LayoutDocument, LayoutAnchorable |
Document, Tool |
LayoutAnchorSide, LayoutAnchorGroup |
Projections over native pinned lists |
| Document/tool floating models | DockWindow and a floating RootDock |
LayoutDocumentFloatingWindowControl, LayoutAnchorableFloatingWindowControl |
Typed subclasses of the actual native HostWindow |
DockingManager |
A themed DockControl using the live root and factory |
LayoutDocumentItem, LayoutAnchorableItem |
Container state and commands over the same Document/Tool |
LayoutFactory extends the native Avalonia factory at the window-close and
batch-floating boundaries. It composes lifecycle helpers to honor AvalonDock
capabilities and return positions, then delegates actual movement to Dock.
LayoutFloatingWindowPresenter, the host factory, and scoped templates manage
floating presentation and preserve the manager's template lookup scope.
Window command adapters observe the current item containers and delegate to
their commands. Host closure uses IDockWindowClosePolicy to preserve content;
the compatibility hooks handle capability and hide checks before Closing.
The optional IDockWindowHostFactory supplies the model at initialization and
when a host is recreated. LayoutFactory preserves the native ID-based and
application locators while making that model available to the manager's host
factory; nested requests and exceptions restore the previous request. A
separate registry observes native host identities and floating-model membership
to publish control additions and removals.
Dock's optional IHostWindowCloseState lets its model adapter and factory query
completed closure before releasing host and ownership links. Native and managed
hosts implement it; hidden or canceled hosts report that they are still live.
Custom hosts without this interface keep the existing unconditional exit behavior.
LayoutFloatingWindowCreation scopes per-content creation options around native
factory movement. The host prepares its native layout and geometry while
deferring presentation until the application shows it. Floating Dock controls
inherit the manager's host factory, including when a floating window is shown
before the main view.
The native content template creates or recycles a public document/tool content
control through compiled XAML. Its model binding resolves the corresponding
LayoutItem from the factory, and its theme presents the existing LayoutItem.View.
Weak model subscriptions avoid retaining discarded controls. Item detachment
clears generated controls while temporary native moves preserve the container.
That presenter selects manager content templates and delegates
inline controls and Dock template content to the native model's template path.
The item registry keeps containers in the manager's logical scope and applies
selected control themes. Compiled container bindings synchronize metadata and
capabilities with those same native models.
Header and tool-title template adapters translate native document/tool models
to their existing public wrappers before applying compiled templates. Their presenters
remain inside Dock's tab controls, auto-hide buttons, and tool chrome. Tool titles
observe the native pane's selected model. Closed containers release their
header bindings and native presentation parents.
Floating document captions bind to the public floating window's RootDocument
and compose native HostWindow state and drag handling. Pane content templates
pass public layout wrappers into their deferred presenters, then resolve the
same native-backed LayoutItem.View. This keeps native models out of competing
logical-parent relationships when tab and content hosts detach and reattach.
The native factory optionally implements IDockOperationObserver.
DockManager.ValidateDockable opens its scope only for validated execution,
before the first collection mutation. The adapter shares that scope with layout
methods and factory calls, suppressing duplicate notifications from internal
moves while allowing event handlers to operate other contents. Scopes release
their references on success or failure. Routed-event class handlers preserve
the public layout source through Avalonia's event routing without reflection.
The document list uses the native tab strip's right-content slot and Avalonia's
MenuFlyout. Its sorted entries bind to public layout contents, and their header
and icon presenters share the item container's template refresh and cleanup.
An open document list closes before layout replacement even when Dock recycles
its pane and menu button.
Selection invokes the current container activation command, which delegates
movement and active-content changes to the native factory.
LayoutGridControl<T> exposes direct public panes and native splitters. Real
Avalonia Grid definitions apply public pane lengths and minimums; arranged bounds
update the existing native proportions. IProportionalSplitterHost routes Dock's
splitter input and previews to the public length policy, and
DockableControlTracker supplies native bounds and pointer tracking. Weak grid
registrations invalidate docked and floating views when pane dimensions change. Floating hosts receive scalar splitter settings
through weak registrations, so discarded hosts do not stay alive through manager
property notifications.
XmlLayoutSerializer uses explicit codecs and content callbacks without
runtime type discovery. It restores the facade and its native models together.
The core docking algorithms, input handling, recycling, and control rendering
remain in Dock. See the Dock architecture overview for
the native interaction pipeline.
Sample and validation
From the repository root:
dotnet run --project samples/DockAvalonDockSample
dotnet test tests/Dock.Avalonia.AvalonDock.Tests -c Release
dotnet test tests/Dock.Avalonia.AvalonDock.ImageTests -c Release
The sample demonstrates documents, tool panes, Agenda/Contacts auto-hide strips, floating, and save/load commands. Its XAML adapts Xceed Live Explorer 3.7.0 and includes the original Ms-PL license and attribution. The adapter implementation is new MIT-licensed code.
The main test project covers compiled default/Xceed namespaces, Headless input, native model synchronization, XML, and lifecycle behavior. The image test project uses Headless with Skia to verify full-content drag snapshots, actual decoding, and loopback HTTP/HTTPS loading. The separate NuGet consumer builds against the packed assemblies on .NET 8 and .NET 10. The compatibility checklist records the pinned API/signature/default reviews, implementation evidence, and framework boundaries. Current-package interactive desktop checks remain deferred at the user's request; Headless and rendering results are recorded separately.