Docking groups provide a powerful mechanism for controlling which dockables can be docked together. By assigning the same group identifier to related dockables, you can create isolated docking areas where only specific types of content can be arranged together.
The DockGroup
property is available on all dockables (IDockable
) and allows you to:
Key Behavior:
null
/empty groups (maximum flexibility)// Create documents that can only dock with other documents
var document1 = new Document
{
Id = "Doc1",
Title = "Document 1",
DockGroup = "Documents"
};
var document2 = new Document
{
Id = "Doc2",
Title = "Document 2",
DockGroup = "Documents"
};
// Create tools that can only dock with other tools
var toolA = new Tool
{
Id = "ToolA",
Title = "Tool A",
DockGroup = "Tools"
};
var toolB = new Tool
{
Id = "ToolB",
Title = "Tool B",
DockGroup = "Tools"
};
// Create an unrestricted dock that only accepts other unrestricted dockables
var unrestrictedDock = new ToolDock
{
Id = "UnrestrictedDock",
Title = "Unrestricted Dock",
DockGroup = null // Only accepts other null-group dockables
};
// Create unrestricted tools that can only dock in unrestricted areas
var unrestrictedTool = new Tool
{
Id = "UnrestrictedTool",
Title = "Unrestricted Tool",
DockGroup = null // Can only dock with other null-group targets
};
<RootDock>
<DocumentDock DockGroup="Documents">
<Document Id="Doc1" Title="Document 1" DockGroup="Documents" />
<Document Id="Doc2" Title="Document 2" DockGroup="Documents" />
</DocumentDock>
<ToolDock DockGroup="LeftTools" Alignment="Left">
<Tool Id="Explorer" Title="Explorer" DockGroup="LeftTools" />
<Tool Id="Outline" Title="Outline" DockGroup="LeftTools" />
</ToolDock>
<ToolDock DockGroup="RightTools" Alignment="Right">
<Tool Id="Properties" Title="Properties" DockGroup="RightTools" />
<Tool Id="Toolbox" Title="Toolbox" DockGroup="RightTools" />
</ToolDock>
</RootDock>
Docking groups support inheritance through the ownership hierarchy. If a dockable doesn’t have an explicit DockGroup
set, the system walks up the ownership chain to find an inherited group.
// Create a parent dock with a group
var parentDock = new ToolDock
{
Id = "ParentDock",
Title = "Parent Tools",
DockGroup = "InheritedGroup"
};
// Child dockables without explicit groups inherit from parent
var childTool1 = new Tool
{
Id = "Child1",
Title = "Child Tool 1",
DockGroup = null, // Will inherit "InheritedGroup"
Owner = parentDock
};
var childTool2 = new Tool
{
Id = "Child2",
Title = "Child Tool 2",
DockGroup = null, // Will inherit "InheritedGroup"
Owner = parentDock
};
parentDock.VisibleDockables = new List<IDockable> { childTool1, childTool2 };
The inheritance mechanism allows you to set a group on a container dock and have all child dockables automatically belong to that group, simplifying configuration.
Understanding the difference between global and local docking is crucial for working with docking groups:
Local docking occurs when dockables are moved within the same dock container or between closely related areas. Examples include:
Validation: Uses strict group rules - both source and target must be compatible according to their groups.
Global docking occurs when dockables are moved across different dock containers, windows, or major workspace areas. Examples include:
Validation: Uses simplified rules based only on the source dockable’s group:
The system automatically determines which validation mode to use based on the operation context. This design allows maximum flexibility for workspace organization (global docking) while maintaining strict isolation where needed (local docking).
The docking system enforces different rules depending on whether the operation is local docking (within the same dock container) or global docking (across different dock containers/windows):
Global docking uses simplified validation that only considers the source dockable’s group:
Dockables with null
or empty groups can dock globally anywhere, regardless of target content:
// This can dock globally into any proportional dock area
var flexibleTool = new Tool { DockGroup = null };
// This can also dock globally anywhere
var anotherFlexible = new Tool { DockGroup = "" };
// Even if the target has grouped content, non-grouped sources can still dock globally
var targetWithGroupedContent = new ProportionalDock
{
VisibleDockables = new List<IDockable>
{
new Tool { DockGroup = "SpecificGroup" }
}
};
// flexibleTool can still dock globally into targetWithGroupedContent
Dockables with non-null groups cannot dock globally at all (blocked entirely):
// These CANNOT dock globally anywhere
var groupedTool = new Tool { DockGroup = "SomeGroup" };
// Blocked from global docking regardless of target
var emptyTarget = new ProportionalDock { VisibleDockables = new List<IDockable>() };
var groupedTarget = new ProportionalDock
{
VisibleDockables = new List<IDockable>
{
new Tool { DockGroup = "SomeGroup" }
}
};
// groupedTool cannot dock globally into either target
Local docking uses strict validation that requires compatible groups between source and target:
Dockables with the same non-null group can be docked together locally:
// These can dock together locally
var tool1 = new Tool { DockGroup = "GroupA" };
var tool2 = new Tool { DockGroup = "GroupA" };
Dockables with null
or empty groups can dock locally with other null
/empty group dockables:
// These can dock together locally
var flexibleTool1 = new Tool { DockGroup = null };
var flexibleTool2 = new Tool { DockGroup = "" };
Dockables with different non-null groups cannot be docked together locally:
// These CANNOT dock together locally
var toolA = new Tool { DockGroup = "GroupA" };
var toolB = new Tool { DockGroup = "GroupB" };
One grouped + one ungrouped = local docking denied (prevents contamination and breakouts):
// These CANNOT dock together locally
var groupedTool = new Tool { DockGroup = "GroupA" };
var ungroupedTool = new Tool { DockGroup = null };
Empty docks (with no visible dockables) accept any dockable regardless of group for local docking:
var emptyDock = new ToolDock
{
DockGroup = "SpecificGroup",
VisibleDockables = new List<IDockable>() // Empty
};
// Any dockable can be dropped into an empty dock locally
The key difference is that global docking is designed to be more permissive for non-grouped content, allowing flexible workspace organization, while local docking maintains strict group isolation to prevent accidental mixing of incompatible content.
public class MainDockFactory : Factory
{
public override IRootDock CreateLayout()
{
// Document area - only documents can dock here
var documentsPane = new DocumentDock
{
Id = "DocumentsPane",
Title = "Documents",
DockGroup = "Documents"
};
// Left panel area - only left tools can dock here
var leftPanelDock = new ToolDock
{
Id = "LeftPanelDock",
Title = "Left Tools",
DockGroup = "LeftPanels",
Alignment = Alignment.Left
};
// Right panel area - only right tools can dock here
var rightPanelDock = new ToolDock
{
Id = "RightPanelDock",
Title = "Right Tools",
DockGroup = "RightPanels",
Alignment = Alignment.Right
};
// Bottom panel area - only bottom tools can dock here
var bottomPanelDock = new ToolDock
{
Id = "BottomPanelDock",
Title = "Bottom Tools",
DockGroup = "BottomPanels",
Alignment = Alignment.Bottom
};
// Create specific tools for each area
var solutionExplorer = new Tool
{
Id = "SolutionExplorer",
Title = "Solution Explorer",
DockGroup = "LeftPanels"
};
var properties = new Tool
{
Id = "Properties",
Title = "Properties",
DockGroup = "RightPanels"
};
var errorList = new Tool
{
Id = "ErrorList",
Title = "Error List",
DockGroup = "BottomPanels"
};
// Assign tools to their respective docks
leftPanelDock.VisibleDockables = CreateList<IDockable>(solutionExplorer);
rightPanelDock.VisibleDockables = CreateList<IDockable>(properties);
bottomPanelDock.VisibleDockables = CreateList<IDockable>(errorList);
return CreateRootDock(documentsPane, leftPanelDock, rightPanelDock, bottomPanelDock);
}
}
// Create areas with specific purposes
var codeEditorsGroup = "CodeEditors";
var designersGroup = "Designers";
var debuggingGroup = "Debugging";
// Code editors - only code-related content
var codeEditor1 = new Document
{
Id = "Code1",
Title = "Program.cs",
DockGroup = codeEditorsGroup
};
var codeEditor2 = new Document
{
Id = "Code2",
Title = "MainWindow.cs",
DockGroup = codeEditorsGroup
};
// Designers - only design-related content
var xamlDesigner = new Document
{
Id = "Designer1",
Title = "MainWindow.xaml",
DockGroup = designersGroup
};
var formDesigner = new Document
{
Id = "Designer2",
Title = "Form1.cs[Design]",
DockGroup = designersGroup
};
// Debugging tools - only debugging content
var debugOutput = new Tool
{
Id = "DebugOutput",
Title = "Debug Output",
DockGroup = debuggingGroup
};
var watchWindow = new Tool
{
Id = "Watch",
Title = "Watch",
DockGroup = debuggingGroup
};
// Flexible tools - can dock anywhere
var searchResults = new Tool
{
Id = "Search",
Title = "Search Results",
DockGroup = null // Flexible
};
The DockGroup
is also available as an attached property through DockProperties.DockGroup
. This provides additional flexibility for setting groups in XAML templates and enables inheritance down the visual tree:
<Window xmlns:dock="clr-namespace:Dock.Settings;assembly=Dock.Settings"
dock:DockProperties.DockGroup="WindowGroup">
<!-- All child dockables inherit "WindowGroup" -->
<DockControl>
<!-- Layout content -->
</DockControl>
</Window>
// Set group via attached property
DockProperties.SetDockGroup(myControl, "MyGroup");
// Get group via attached property
string? group = DockProperties.GetDockGroup(myControl);
The attached property supports inheritance, so setting it on a parent container automatically applies the group to all child elements unless they have their own explicit group.
The DockGroupValidator
class contains the core validation logic for docking groups, with separate methods for global and local docking:
/// <summary>
/// Validates if a dockable can be docked globally based on docking groups.
/// For global docking, we only prevent it if the source dockable has a docking group set.
/// Non-grouped dockables should always be allowed to dock globally, regardless of target content.
/// </summary>
public static bool ValidateGlobalDocking(IDockable sourceDockable, IDock targetDock)
{
var sourceGroup = GetEffectiveDockGroup(sourceDockable);
// For global docking, only restrict if source has a docking group set
// Always allow global docking for non-grouped dockables
return string.IsNullOrEmpty(sourceGroup);
}
/// <summary>
/// Validates if two dockables can be docked together based on their docking groups.
/// Uses strict validation rules for local docking operations.
/// </summary>
public static bool ValidateDockingGroups(IDockable sourceDockable, IDockable targetDockable)
{
var sourceGroup = GetEffectiveDockGroup(sourceDockable);
var targetGroup = GetEffectiveDockGroup(targetDockable);
// Strict docking group compatibility rules for local docking:
// 1. Non-grouped can dock with non-grouped only
// 2. Grouped can dock with same group only
// 3. Different groups are incompatible
// 4. Mixed states (grouped + non-grouped) are incompatible
var sourceHasGroup = !string.IsNullOrEmpty(sourceGroup);
var targetHasGroup = !string.IsNullOrEmpty(targetGroup);
if (!sourceHasGroup && !targetHasGroup)
{
return true; // Both non-grouped - compatible
}
if (!sourceHasGroup && targetHasGroup)
{
return false; // Non-grouped can't dock into grouped dockables
}
if (sourceHasGroup && !targetHasGroup)
{
return false; // Grouped can't dock into non-grouped dockables
}
// Both are grouped - they must have the same group
return string.Equals(sourceGroup, targetGroup, StringComparison.Ordinal);
}
/// <summary>
/// Validates if a dockable can be docked into a dock based on docking groups.
/// The source must be compatible with all existing dockables in the target dock.
/// </summary>
public static bool ValidateDockingGroupsInDock(IDockable sourceDockable, IDock targetDock)
{
// If the target dock has no visible dockables, allow the operation
if (targetDock.VisibleDockables?.Count == 0)
{
return true;
}
// Check compatibility with each existing dockable in the target dock
if (targetDock.VisibleDockables != null)
{
foreach (var existingDockable in targetDock.VisibleDockables)
{
if (!ValidateDockingGroups(sourceDockable, existingDockable))
{
return false;
}
}
}
return true;
}
The system resolves the effective group for a dockable by walking up the ownership hierarchy:
private static string? GetEffectiveDockGroup(IDockable dockable)
{
var current = dockable;
// Walk up the hierarchy until we find a group or reach the root
while (current != null)
{
if (!string.IsNullOrEmpty(current.DockGroup))
{
return current.DockGroup;
}
current = current.Owner;
}
return null;
}
Choose clear, descriptive names for your groups:
// Good
DockGroup = "DocumentEditors"
DockGroup = "DebugTools"
DockGroup = "DesignSurfaces"
// Avoid
DockGroup = "Group1"
DockGroup = "A"
Group dockables that logically belong together:
// Related editing tools
var groupEditing = "Editing";
var findTool = new Tool { DockGroup = groupEditing };
var replaceTool = new Tool { DockGroup = groupEditing };
// Related debugging tools
var groupDebug = "Debug";
var breakpointsTool = new Tool { DockGroup = groupDebug };
var watchTool = new Tool { DockGroup = groupDebug };
Set groups on container docks to automatically group all children:
var debugDock = new ToolDock
{
DockGroup = "Debug", // All child tools inherit this
VisibleDockables = CreateList<IDockable>(
new Tool { Id = "Breakpoints" }, // Inherits "Debug"
new Tool { Id = "Watch" }, // Inherits "Debug"
new Tool { Id = "Output" } // Inherits "Debug"
)
};
Include some dockables with null
groups for maximum flexibility:
var flexibleTool = new Tool
{
Id = "FlexTool",
Title = "Flexible Tool",
DockGroup = null // Can dock globally anywhere and locally with other ungrouped dockables
};
Verify that your group setup provides the expected docking behavior:
// Test that validation works correctly
var dockManager = new DockManager(new DockService());
var canDock = dockManager.ValidateTool(sourceTool, targetDock,
DragAction.Move, DockOperation.Fill, false);
Assert.True(canDock); // or Assert.False for restricted cases
// Documents can only dock locally with other documents
// But CANNOT dock globally at all (grouped sources are blocked from global docking)
var doc = new Document { DockGroup = "Documents" };
// Tools can only dock locally with other tools
// But CANNOT dock globally at all (grouped sources are blocked from global docking)
var tool = new Tool { DockGroup = "Tools" };
// They cannot be mixed locally due to different groups
// And cannot participate in global docking at all due to having groups
// Design mode - only design-related content (local docking only)
var designMode = "Design";
// Debug mode - only debugging content (local docking only)
var debugMode = "Debug";
// Switch groups based on current mode
foreach (var dockable in allDockables)
{
dockable.DockGroup = isDesignMode ? designMode : debugMode;
// Note: Setting groups blocks global docking for these dockables
// They can only dock locally with others in the same mode
}
// For flexible workspace organization, consider ungrouped tools:
var flexibleTool = new Tool
{
DockGroup = null // Can dock globally anywhere and locally with other ungrouped tools
};
// Developer tools
var devGroup = "Developer";
// Designer tools
var designGroup = "Designer";
// Admin tools
var adminGroup = "Administrator";
// Assign based on user role
var userRole = GetCurrentUserRole();
myTool.DockGroup = userRole.ToString();
Q: My dockables won’t dock together even though they have the same group A: Check for:
Q: Dockables with null groups aren’t docking
A: Verify:
CanDrop
property is trueCanDrag
being false)Q: Group inheritance isn’t working A: Ensure:
Owner
propertyUse the validation methods to test group compatibility:
// Check effective groups
var sourceGroup = DockGroupValidator.GetEffectiveDockGroup(sourceDockable);
var targetGroup = DockGroupValidator.GetEffectiveDockGroup(targetDockable);
Console.WriteLine($"Source: '{sourceGroup}', Target: '{targetGroup}'");
// Test local docking validation
var isValidLocal = DockGroupValidator.ValidateDockingGroups(sourceDockable, targetDockable);
Console.WriteLine($"Can dock locally: {isValidLocal}");
// Test global docking validation (if target is a dock)
if (targetDockable is IDock targetDock)
{
var isValidGlobal = DockGroupValidator.ValidateGlobalDocking(sourceDockable, targetDock);
Console.WriteLine($"Can dock globally: {isValidGlobal}");
}
// Test docking into a specific dock
if (targetDockable is IDock targetDockForDockValidation)
{
var isValidInDock = DockGroupValidator.ValidateDockingGroupsInDock(sourceDockable, targetDockForDockValidation);
Console.WriteLine($"Can dock into dock: {isValidInDock}");
}