diff --git a/ILSpy/App.axaml b/ILSpy/App.axaml index 0c93eef4e..7cda7895e 100644 --- a/ILSpy/App.axaml +++ b/ILSpy/App.axaml @@ -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 @@ - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index 4c6e3c50a..a951a7000 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -144,7 +144,7 @@ namespace ILSpy.Docking NavigateToHistoryCommand = new RelayCommand(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 // 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 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)) diff --git a/ILSpy/Docking/ILSpyDockFactory.cs b/ILSpy/Docking/ILSpyDockFactory.cs index 8683a047f..0162fc97d 100644 --- a/ILSpy/Docking/ILSpyDockFactory.cs +++ b/ILSpy/Docking/ILSpyDockFactory.cs @@ -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 this.panes = registry.Panes; } + /// + /// Populate the locator dictionaries with explicit id-to-factory mappings before the + /// base init wires owners/factories down the tree, rather than relying on + /// 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. + /// + /// + /// Tool panes are [Shared] MEF singletons, so the lambdas in + /// / + /// 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 + /// (root.Navigate.Execute("Documents")). + /// + /// + public override void InitLayout(IDockable layout) + { + ContextLocator = new Dictionary>(); + DockableLocator = new Dictionary>(); + HostWindowLocator = new Dictionary> { + [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); + } + /// /// Serializes to using /// . Best-effort: any IO or serialization diff --git a/ILSpy/ViewModels/MainWindowViewModel.cs b/ILSpy/ViewModels/MainWindowViewModel.cs index 184db113f..52cc61c84 100644 --- a/ILSpy/ViewModels/MainWindowViewModel.cs +++ b/ILSpy/ViewModels/MainWindowViewModel.cs @@ -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 public UpdatePanelViewModel UpdatePanel { get; } - public IFactory DockFactory => DockWorkspace.Factory; - public IRootDock DockLayout => DockWorkspace.Layout; public System.Collections.Generic.IReadOnlyList ToolPaneMenuItems => DockWorkspace.ToolPaneMenuItems; diff --git a/ILSpy/Views/MainWindow.axaml b/ILSpy/Views/MainWindow.axaml index b526e738c..3ed9d08ee 100644 --- a/ILSpy/Views/MainWindow.axaml +++ b/ILSpy/Views/MainWindow.axaml @@ -46,9 +46,6 @@ Margin="8,0" Name="statusLabel" Text="Stand by..." /> - +