-
Notifications
You must be signed in to change notification settings - Fork 143
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.
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
| 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 beforedoc-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
| 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 |
| 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 |
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
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);
}// 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);// 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");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
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);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"]— serializedEditorConfigDto
- Docking Engine — how ContentIds are serialized in the layout
-
Editor Registry —
FindFactory()resolution -
Project System —
IProjectItemand its stable ID -
Plugin System —
IUIRegistry.RegisterPanel()
✨ Wpf HexEditor user control, by Derek Tremblay (derektremblay666@gmail.com) coded for your fun! 😊🤟
- API Reference
- Performance
- Services
- Core Components
- ByteProvider
- Rendering Engine
- Search Architecture
- 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)