Browse Source

Wire the dock factory's locators eagerly before the chrome

Build the layout and call InitLayout up front in DockWorkspace instead of
letting DockControl run it post-template-apply, and populate the locators
explicitly: tool panes map their id to the [Shared] singleton pane, the
Documents dock and main tab get entries for navigate-by-id, and
HostWindowLocator is registered so panes can float into their own windows.
Doing it before the chrome attaches keeps the layout's owner/factory
wiring ahead of the first content realisation.

Also drops the now-redundant per-VM DataTemplate blocks from App.axaml --
the single ViewLocator already resolves them -- and the unused
MainWindowViewModel.DockFactory property and DockControl init flags.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
7ed97e4a75
  1. 28
      ILSpy/App.axaml
  2. 9
      ILSpy/Docking/DockWorkspace.cs
  3. 48
      ILSpy/Docking/ILSpyDockFactory.cs
  4. 3
      ILSpy/ViewModels/MainWindowViewModel.cs
  5. 5
      ILSpy/Views/MainWindow.axaml

28
ILSpy/App.axaml

@ -8,7 +8,6 @@ @@ -8,7 +8,6 @@
xmlns:compare="using:ILSpy.Compare"
xmlns:textView="using:ILSpy.TextView"
xmlns:metaViews="using:ILSpy.Views"
xmlns:vm="using:ILSpy.ViewModels"
xmlns:dockTheme="using:Dock.Avalonia.Themes.Simple"
xmlns:recycling="using:Avalonia.Controls.Recycling"
xmlns:docking="using:ILSpy.Docking"
@ -47,30 +46,9 @@ @@ -47,30 +46,9 @@
</Application.Resources>
<Application.DataTemplates>
<DataTemplate DataType="tree:AssemblyTreeModel">
<tree:AssemblyListPane />
</DataTemplate>
<DataTemplate DataType="search:SearchPaneModel">
<search:SearchPane />
</DataTemplate>
<DataTemplate DataType="analyzers:AnalyzerTreeViewModel">
<analyzers:AnalyzerTreeView />
</DataTemplate>
<DataTemplate DataType="vm:ContentTabPage">
<metaViews:ContentTabPageView />
</DataTemplate>
<DataTemplate DataType="textView:DecompilerTabPageModel">
<textView:DecompilerTextView />
</DataTemplate>
<DataTemplate DataType="vm:MetadataTablePageModel">
<metaViews:MetadataTablePage />
</DataTemplate>
<DataTemplate DataType="vm:DebugStepsPaneModel">
<metaViews:DebugSteps />
</DataTemplate>
<DataTemplate DataType="compare:CompareTabPageModel">
<compare:CompareView />
</DataTemplate>
<!-- ViewLocator handles every dockable VM (tool panes, document tab content,
ContentTabPage) plus any ViewModelBase-derived viewmodel, with one IDataTemplate
instance for all of them (see ViewLocator.cs). -->
<local:ViewLocator/>
</Application.DataTemplates>

9
ILSpy/Docking/DockWorkspace.cs

@ -144,7 +144,7 @@ namespace ILSpy.Docking @@ -144,7 +144,7 @@ namespace ILSpy.Docking
NavigateToHistoryCommand = new RelayCommand<NavigationEntry>(NavigateToHistory,
entry => entry != null && (history.BackEntries.Contains(entry) || history.ForwardEntries.Contains(entry)));
ShowSearchCommand = new RelayCommand(ExecuteShowSearch);
using (ILSpy.AppEnv.AppLog.Phase("ILSpyDockFactory ctor + CreateLayout"))
using (ILSpy.AppEnv.AppLog.Phase("ILSpyDockFactory ctor + CreateLayout + InitLayout"))
{
factory = new ILSpyDockFactory(toolPaneRegistry);
// Prefer the user's saved layout (ILSpy.Layout.json sidecar next to
@ -152,6 +152,11 @@ namespace ILSpy.Docking @@ -152,6 +152,11 @@ namespace ILSpy.Docking
// or it failed to deserialize. The fallback path is the same shape the
// app uses on first launch.
Layout = factory.LoadLayout(GetLayoutFilePath()) ?? factory.CreateLayout();
// Build the layout, then InitLayout BEFORE the chrome ever sees it, rather than
// letting the DockControl's InitializeFactory / InitializeLayout flags run it
// post-template-apply. Wiring owners/factories/locators up front means the
// layout is fully resolvable before the first DeferredContentControl attaches.
factory.InitLayout(Layout);
}
assemblyTreeModel.PropertyChanged += OnAssemblyTreePropertyChanged;
@ -160,8 +165,6 @@ namespace ILSpy.Docking @@ -160,8 +165,6 @@ namespace ILSpy.Docking
ShowSelectedNode();
};
languageService.PropertyChanged += OnLanguagePropertyChanged;
// Layout/factory initialization (locators, parent/factory wiring) is done by
// the DockControl in MainWindow.axaml via InitializeFactory/InitializeLayout.
ToolPaneMenuItems = toolPaneRegistry.Panes
.Select(p => new ToolPaneMenuItem(p.Pane, factory))

48
ILSpy/Docking/ILSpyDockFactory.cs

@ -21,6 +21,7 @@ using System.Collections.Generic; @@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dock.Avalonia.Controls;
using Dock.Model.Controls;
using Dock.Model.Core;
using Dock.Model.Mvvm;
@ -49,6 +50,53 @@ namespace ILSpy.Docking @@ -49,6 +50,53 @@ namespace ILSpy.Docking
this.panes = registry.Panes;
}
/// <summary>
/// Populate the locator dictionaries with explicit id-to-factory mappings before the
/// base init wires owners/factories down the tree, rather than relying on
/// <see cref="DockControl.InitializeFactory"/> to populate them post-template-apply.
/// Wiring the locators (and the HostWindowLocator) up front means the layout is fully
/// resolvable before the chrome realises any content.
///
/// <para>
/// Tool panes are <c>[Shared]</c> MEF singletons, so the lambdas in
/// <see cref="IFactory.ContextLocator"/> / <see cref="IFactory.DockableLocator"/>
/// can safely return the cached pane instance for both context and dockable
/// lookup. Documents and the root dock get explicit entries for navigate-by-id
/// (<c>root.Navigate.Execute("Documents")</c>).
/// </para>
/// </summary>
public override void InitLayout(IDockable layout)
{
ContextLocator = new Dictionary<string, Func<object?>>();
DockableLocator = new Dictionary<string, Func<IDockable?>>();
HostWindowLocator = new Dictionary<string, Func<IHostWindow?>> {
[nameof(IDockWindow)] = () => new HostWindow()
};
// Tool panes: the VM is its own context (no separate model class), and the
// dockable IS the pane instance, so both locators map id → same singleton.
foreach (var entry in panes)
{
var pane = entry.Pane;
if (string.IsNullOrEmpty(pane.Id))
continue;
ContextLocator[pane.Id] = () => pane;
DockableLocator[pane.Id] = () => pane;
}
// Documents dock + the persistent main content tab — these are the structural
// slots ShowSelectedNode / OpenNewTab target. Carve-out tabs (per-node spawned
// ContentTabPages) don't get id mappings because they're transient.
if (Documents is { Id: { Length: > 0 } docsId } docs)
DockableLocator[docsId] = () => docs;
if (MainTab is { Id: { Length: > 0 } tabId } mainTab)
DockableLocator[tabId] = () => mainTab;
if (layout is IRootDock { Id: { Length: > 0 } rootId } root)
DockableLocator[rootId] = () => root;
base.InitLayout(layout);
}
/// <summary>
/// Serializes <paramref name="layout"/> to <paramref name="path"/> using
/// <see cref="ILSpyDockJson.Options"/>. Best-effort: any IO or serialization

3
ILSpy/ViewModels/MainWindowViewModel.cs

@ -19,7 +19,6 @@ @@ -19,7 +19,6 @@
using System.Composition;
using Dock.Model.Controls;
using Dock.Model.Core;
using ILSpy.AssemblyTree;
using ILSpy.Docking;
@ -41,8 +40,6 @@ namespace ILSpy.ViewModels @@ -41,8 +40,6 @@ namespace ILSpy.ViewModels
public UpdatePanelViewModel UpdatePanel { get; }
public IFactory DockFactory => DockWorkspace.Factory;
public IRootDock DockLayout => DockWorkspace.Layout;
public System.Collections.Generic.IReadOnlyList<ToolPaneMenuItem> ToolPaneMenuItems => DockWorkspace.ToolPaneMenuItems;

5
ILSpy/Views/MainWindow.axaml

@ -46,9 +46,6 @@ @@ -46,9 +46,6 @@
Margin="8,0" Name="statusLabel" Text="Stand by..." />
</Border>
<dock:DockControl Layout="{Binding DockLayout}"
Factory="{Binding DockFactory}"
InitializeFactory="True"
InitializeLayout="True" />
<dock:DockControl Layout="{Binding DockLayout}" />
</DockPanel>
</Window>

Loading…
Cancel
Save