Skip to content

ContentId Routing

abbaye edited this page Mar 19, 2026 · 2 revisions

ContentId Routing

Every tab and dockable panel in the IDE is identified by a ContentId string. When the docking layout is restored at startup, BuildContentForItem() maps each ContentId back to its WPF FrameworkElement.


How It Works

sequenceDiagram
    participant Layout as DockLayoutSerializer
    participant Main as BuildContentForItem()
    participant Factory as IEditorFactory / Panel factory
    participant WPF as FrameworkElement

    Note over Layout: App restores saved layout JSON
    Layout->>Main: ContentId = "doc-proj-hex-editor-{guid}"
    Main->>Main: Parse prefix + factoryId + itemId
    Main->>Factory: editorRegistry.FindFactory(filePath, "hex-editor")
    Factory->>WPF: Create() → new HexEditorControl
    WPF-->>Layout: tab content ready
Loading

ContentId Reference

Document Editors

ContentId pattern Resolves to
doc-welcome WelcomePanel
doc-proj-{itemId} Auto editor selection (format-aware)
doc-proj-{factoryId}-{itemId} Specific editor factory
doc-standalone-{filePath} Standalone file (not in a project)
doc-nuget-solution-{solutionName} NuGet Solution Manager

Routing order matters: doc-nuget-solution- must be matched before doc-nuget- in the switch to avoid misrouting.

Examples:

doc-proj-3f8a2c1b-4d5e-6f7a-8b9c-0d1e2f3a4b5c
doc-proj-hex-editor-3f8a2c1b-4d5e-6f7a-8b9c-0d1e2f3a4b5c
doc-proj-code-editor-3f8a2c1b-4d5e-6f7a-8b9c-0d1e2f3a4b5c
doc-proj-xaml-designer-7a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d
doc-nuget-solution-MySolution

IDE Panels

ContentId Panel
panel-terminal TerminalPanel
panel-plugin-monitoring PluginMonitoringPanel
panel-plugin-manager PluginManagerControl
panel-solution-explorer SolutionExplorerPanel
panel-parsed-fields ParsedFieldsPanel
panel-properties PropertiesPanel
panel-output OutputPanel
panel-errors ErrorPanel
panel-assembly-explorer AssemblyExplorerPanel
panel-nuget-solution NuGetSolutionManagerDocument

XAML Designer Panels

ContentId Panel
panel-xd-toolbox XAML Toolbox
panel-xd-resource-browser Resource Browser
panel-xd-design-data Design-Time Data
panel-xd-animation Animation Timeline
panel-xd-live-visual-tree Live Visual Tree
panel-xd-property-grid Property Grid
panel-xd-state-machine State Machine
panel-xd-style-editor Style Editor
panel-xd-history Design History

Plugin-Registered Panels

Plugins register panels via IUIRegistry.RegisterPanel(id, title, factory). The ContentId becomes panel-{id}:

panel-my-panel          ← registered as "my-panel"
panel-data-inspector    ← DataInspector first-party plugin
panel-structure-overlay ← StructureOverlay first-party plugin

BuildContentForItem() Internals

Located in MainWindow.Editors.cs. The routing switch (order matters):

private FrameworkElement? BuildContentForItem(string contentId) => contentId switch
{
    "doc-welcome"                  => CreateWelcomePanelContent(),
    "panel-terminal"               => CreateTerminalPanelContent(),
    "panel-plugin-monitoring"      => CreatePluginMonitorPanelContent(),
    "panel-plugin-manager"         => CreatePluginManagerContent(),
    "panel-solution-explorer"      => CreateSolutionExplorerContent(),
    "panel-parsed-fields"          => CreateParsedFieldsContent(),
    "panel-properties"             => CreatePropertiesContent(),
    "panel-output"                 => CreateOutputContent(),
    "panel-errors"                 => CreateErrorPanelContent(),
    "panel-assembly-explorer"      => CreateAssemblyExplorerContent(),
    // XAML Designer panels
    "panel-xd-toolbox"             => CreateXdToolboxContent(),
    "panel-xd-resource-browser"    => CreateXdResourceBrowserContent(),
    "panel-xd-design-data"         => CreateXdDesignDataContent(),
    "panel-xd-animation"           => CreateXdAnimationContent(),
    "panel-xd-history"             => CreateXdHistoryContent(),
    // NuGet — match solution BEFORE generic doc-nuget-
    var id when id.StartsWith("doc-nuget-solution-") => CreateNuGetSolutionManagerContent(id),
    _                              => ResolveDocumentContent(contentId)
};

ResolveDocumentContent() handles doc-proj-* and doc-standalone-* patterns:

private FrameworkElement? ResolveDocumentContent(string contentId)
{
    // "doc-proj-{factoryId}-{itemId}" or "doc-proj-{itemId}"
    if (contentId.StartsWith("doc-proj-"))
    {
        var parts   = contentId["doc-proj-".Length..].Split('-', 2);
        var itemId  = parts[^1];
        var factId  = parts.Length == 2 ? parts[0] : null;
        var item    = solutionManager.FindItemById(itemId);
        if (item is null) return null;

        var factory = editorRegistry.FindFactory(item.FilePath, factId
            ?? GetPreferredEditorId(item.FilePath));
        return (FrameworkElement?)factory?.Create();
    }
    // plugin-registered panels
    return pluginUIRegistry.TryBuildContent(contentId);
}

Opening a Tab

Via the IDE host

// Open a project item (auto-selects best editor)
mainWindow.OpenProjectItem(projectItem);

// Open with a specific editor
mainWindow.OpenProjectItemWithEditor(projectItem, "code-editor");

// Open with XAML Designer
mainWindow.OpenProjectItemWithEditor(projectItem, "xaml-designer");

// Open a standalone file
mainWindow.OpenStandaloneFile(@"C:\data\firmware.bin");

// Open NuGet Solution Manager
mainWindow.OpenNuGetSolutionManager(solutionName);

Via a plugin

// Open or focus a panel registered by the plugin
context.UIRegistry.ShowDockablePanel("my-panel");

// Focus an existing panel
context.UIRegistry.FocusPanel("panel-terminal");

// Toggle a panel
context.UIRegistry.TogglePanel("panel-output");

// Check if a panel/tab exists (prevent duplicate)
bool exists = context.UIRegistry.Exists("panel-my-panel");

Preventing Duplicate Tabs

Before creating a new tab, OpenStandaloneFileWithEditor() scans the active layout for an existing tab with the same FilePath and matching ActiveEditorId or ForceEditorId. If found, the existing tab is focused rather than a duplicate created.

flowchart TD
    Open["OpenStandaloneFile(path)"]
    Scan["Scan _layout for existing\ntab with same FilePath"]
    Found{"Found?"}
    Focus["Focus existing tab"]
    Create["Create new tab\nwith new ContentId"]

    Open --> Scan
    Scan --> Found
    Found -->|Yes| Focus
    Found -->|No| Create
Loading

For plugin-registered documents, use IUIRegistry.Exists(uiId) guard:

// In plugin Init():
const string UiId = "panel-assembly-explorer";
if (!context.UIRegistry.Exists(UiId))
    context.UIRegistry.RegisterPanel(UiId, "Assembly Explorer", CreatePanel);

Item Metadata

When an editor tab is created, item.Metadata["ActiveEditorId"] is set to the resolved factory ID. This ensures subsequent layout restores use the same editor for that item:

item.Metadata["ActiveEditorId"] = factory.Descriptor.Id;
// e.g. "hex-editor", "code-editor", "xaml-designer", "json-editor"

Additional metadata keys:

  • item.Metadata["xd.layout"] — XAML Designer split layout mode
  • item.Metadata["ce.foldDepth"] — Code Editor fold depth
  • item.Metadata["EditorConfigJson"] — serialized EditorConfigDto

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