Skip to content

Docking Engine

abbaye edited this page Mar 19, 2026 · 3 revisions

Docking Engine

WpfHexEditor.Shell is a 100% in-house Visual Studio–style docking engine — zero third-party dependency, fully themed.

v0.6.0 rename: previously WpfHexEditor.Docking.Wpf. All namespaces are now WpfHexEditor.Shell.*. The assembly is WpfHexEditor.Shell.dll.


Architecture

flowchart TD
    Host["DockControl\n(XAML: dock:DockControl)"]
    DockTabControl["DockTabControl\nTab strip per zone"]
    AutoHideBar["AutoHideBar\nLeft / Right / Bottom"]
    AutoHideFlyout["AutoHideFlyout\nAnimated slide-in panel"]
    Preview["AutoHideBarHoverPreview\nSnapshot on hover"]
    Serializer["DockLayoutSerializer\nSave / Restore layout"]
    Dto["DockItemDto\nSerializable state"]
    Themes["ResourceDictionary\n8 built-in themes"]

    Host --> DockTabControl
    Host --> AutoHideBar
    AutoHideBar --> AutoHideFlyout
    AutoHideBar --> Preview
    Host --> Serializer
    Serializer --> Dto
    Host --> Themes
Loading

Key Classes

Class Responsibility
IDockHost Contract implemented by MainWindow — entry point for all docking operations
DockTabControl Tab strip that hosts dockable panels; vertical drag triggers float
DockTabHeader Tab header with title, icon, close button and color indicator
AutoHideBar Collapsed panel strip on window edges (left/right/bottom)
AutoHideFlyout Animated slide-in panel shown when clicking an AutoHide tab
AutoHideBarHoverPreview Snapshot thumbnail shown on AutoHide tab hover
DockItemSourceSynchronizer Keeps the ItemsSource of a DockTabControl in sync with the ViewModel list
DockTabEventWirer Connects drag/drop, keyboard, and close events on each tab item

Features

Feature Description
Float Drag any tab out of the strip → independent Window
Dock Drag a floating window over a drop zone → re-docks
Auto-hide Pin/unpin collapses panel to edge strip
Tab groups Multiple groups per zone, each a DockTabControl
Colored tabs Per-tab color picker via TabSettingsDialog
Live themes Switch among 8 themes at runtime — no restart
Layout persistence Serialize entire layout to JSON (DockItemDto) and restore on next launch
Hover preview Hovering an auto-hide tab shows a snapshot of the panel content

XAML Declaration

1. Add the namespace

xmlns:dock="clr-namespace:WpfHexEditor.Shell;assembly=WpfHexEditor.Shell"

2. Place DockControl in your Window

<Window x:Class="MyApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dock="clr-namespace:WpfHexEditor.Shell;assembly=WpfHexEditor.Shell">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />  <!-- Menu / toolbar -->
            <RowDefinition Height="*" />     <!-- Docking area -->
            <RowDefinition Height="Auto" />  <!-- Status bar -->
        </Grid.RowDefinitions>

        <!-- Docking engine — fills the entire editor area -->
        <dock:DockControl Grid.Row="1"
                          x:Name="DockHost"
                          Margin="2" />
    </Grid>
</Window>

3. Wire up the code-behind

public partial class MainWindow : Window
{
    private DockLayoutRoot _layout = null!;
    private DockEngine     _engine = null!;

    private void InitializeDocking()
    {
        // Provide the content factory: maps ContentId → WPF element
        DockHost.ContentFactory    = CreateContentForItem;
        DockHost.TabCloseRequested += OnTabCloseRequested;
        DockHost.ActiveItemChanged += OnActiveDocumentChanged;

        // Assign a layout — DockControl creates its DockEngine internally
        DockHost.Layout = _layout;

        // IMPORTANT: use the engine that DockControl created, not a separate one.
        // Using a different engine leaves DockControl's internal content cache stale on close.
        _engine = DockHost.Engine!;

        DockHost.RebuildVisualTree();
    }

    // Content factory: called on layout restore and new tab creation
    private object? CreateContentForItem(DockItem item) => item.ContentId switch
    {
        "panel-output"   => new OutputPanel(),
        "panel-terminal" => new TerminalPanel(),
        _                => null
    };

    private void OnTabCloseRequested(DockItem item)
    {
        _engine.Close(item);
        DockHost.RebuildVisualTree();
    }

    private void OnActiveDocumentChanged(DockItem? item)
    {
        // Update status bar, menu state, etc.
    }
}

Layout Persistence

The layout is represented as a DockLayoutRoot tree and serialized to JSON via DockLayoutSerializer.

// --- Save ---
string json = DockLayoutSerializer.Serialize(_layout);
File.WriteAllText(layoutPath, json);

// --- Restore ---
DockLayoutRoot layout = DockLayoutSerializer.Deserialize(File.ReadAllText(layoutPath));
DockHost.Layout = layout;
_engine = DockHost.Engine!;
DockHost.RebuildVisualTree();

Auto-save timer (optional)

var timer = new DispatcherTimer(DispatcherPriority.Background)
{
    Interval = TimeSpan.FromSeconds(30)
};
timer.Tick += (_, _) =>
    File.WriteAllText(layoutPath, DockLayoutSerializer.Serialize(_layout));
timer.Start();

DockWorkspace helper

For simpler scenarios, DockWorkspace wraps DockControl and exposes SaveLayout() / LoadLayout() directly:

var workspace = new DockWorkspace(DockHost);

// Save
string json = workspace.SaveLayout();

// Restore
workspace.LoadLayout(json);

The ContentFactory callback maps each ContentId back to a WPF element on restore. See ContentId Routing for the full ID mapping table.


Adding a New Dockable Panel

1. Create the UserControl

public partial class MyPanel : UserControl
{
    public MyPanel() => InitializeComponent();
}

2. Register a ContentId

In MainWindow.Editors.cs, add a constant and a factory method:

private const string MyPanelContentId = "panel-my-panel";

private FrameworkElement CreateMyPanelContent() => new MyPanel
{
    DataContext = new MyPanelViewModel()
};

3. Wire into BuildContentForItem

case MyPanelContentId:
    return CreateMyPanelContent();

4. Open programmatically

// Add a new item to the layout, then rebuild
var item = new DockItem
{
    ContentId = MyPanelContentId,
    Title     = "My Panel"
};
_engine.AddToGroup(item, targetGroup: null); // null = default document group
DockHost.RebuildVisualTree();

// Via plugin UIRegistry (from a plugin)
context.UIRegistry.ShowDockablePanel("my-panel");

5. Invalidate and refresh a panel

// Force the content factory to recreate the element for a given ContentId
DockHost.InvalidateContent(MyPanelContentId);
DockHost.RebuildVisualTree();

6. Sync layout sizes after programmatic resize

DockHost.SyncLayoutSizes();

Themes

The docking engine reads Dock* brush keys from the active ResourceDictionary:

Key Description
DockBackgroundBrush Main docking area background
DockTabBackgroundBrush Tab strip background
DockTabSelectedBrush Active tab highlight
DockTabHoverBrush Tab hover state
DockTabBorderBrush Tab separator lines
DockTabForegroundBrush Tab title text
DockFloatBorderBrush Floating window border
DockAutoHideBarBrush Auto-hide edge strip
DockDropIndicatorBrush Drop zone highlight during drag

All 8 built-in themes define every Dock* key. See Architecture — Themes for the full theme list.


See Also

Navigation

Getting Started

IDE Documentation

HexEditor Control

Advanced

Development


v0.6.4.75 Highlights

  • whfmt.FileFormatCatalog v1.0.0 NuGet (cross-platform net8.0)
  • 690+ .whfmt definitions (schema v2.3)
  • Structure Editor — block DataGrid, drag-drop, validation, SmartComplete
  • WhfmtBrowser/Catalog panels — browse all embedded formats
  • AI Assistant (5 providers, 25 MCP tools)
  • Tab Groups, Document Structure, Lazy Plugin Loading
  • Window Menu + Win32 Fullscreen (F11)
  • Git Integration UI (changes, history, blame)
  • Shared Undo Engine (HexEditor ↔ CodeEditor)
  • Bracket pair colorization, sticky scroll, peek definition
  • Format detection hardening (thread-safe, crash guard)

Links

Clone this wiki locally