diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index a01a1e180..3dd777cea 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -291,17 +291,22 @@ namespace ILSpy.AssemblyTree /// quietly into "the user never sees a populated icon for assemblies they don't /// touch". /// - /// To strike a middle ground, schedule a one-shot sweep a couple of seconds after - /// the list appears that nudges every to start loading - /// in the background. By that point the active assembly's metadata is usually ready - /// and the user has had a frame or two to interact with the tree. + /// To strike a middle ground, schedule a one-shot sweep that fires after the tree + /// view is on screen () plus a small visibility cooldown. + /// Gating on rather than a wall-clock delay keeps the sweep + /// off slow startups (heavy layout, debugger attached) and ensures the user has + /// genuinely seen the tree before the thread pool fills with sibling-assembly loads. /// void ScheduleBackgroundLoadSweep(AssemblyList list) { _ = Task.Run(async () => { try { - await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); + await TreeReady.ConfigureAwait(false); + // Small grace period after Loaded so the first paint has time to settle + // — kicking off 122 metadata loads the same frame the tree appears would + // steal cycles from the layout pass that just brought it on screen. + await Task.Delay(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false); AppEnv.StartupLog.Mark("Background-load sweep starting"); foreach (var assembly in list.GetAssemblies()) { diff --git a/ILSpy/Docking/ILSpyDockFactory.cs b/ILSpy/Docking/ILSpyDockFactory.cs index 86ed2edc2..f5b688acd 100644 --- a/ILSpy/Docking/ILSpyDockFactory.cs +++ b/ILSpy/Docking/ILSpyDockFactory.cs @@ -55,6 +55,11 @@ namespace ILSpy.Docking Title = "Documents", IsCollapsable = false, Proportion = 0.6, + // Dock's theme template binds against DockCapabilityPolicy.{CanDrag, CanDrop, + // CanPin, CanClose} on every dock. Pre-populate so the binding sees a non-null + // source on first evaluation; otherwise every dock startup logs ~6 [Binding] + // errors as the chrome template is realised. + DockCapabilityPolicy = new DockCapabilityPolicy(), }; Documents = documents; @@ -87,6 +92,7 @@ namespace ILSpy.Docking Orientation = Orientation.Vertical, Proportion = ComputeMiddleColumnProportion(leftToolDock, rightToolDock), VisibleDockables = CreateList(verticalChildren.ToArray()), + DockCapabilityPolicy = new DockCapabilityPolicy(), }; // Horizontal row: left tool dock, middle column, right tool dock — splitters @@ -108,6 +114,7 @@ namespace ILSpy.Docking Id = "MainLayout", Orientation = Orientation.Horizontal, VisibleDockables = CreateList(horizontalChildren.ToArray()), + DockCapabilityPolicy = new DockCapabilityPolicy(), }; var root = CreateRootDock(); @@ -116,6 +123,7 @@ namespace ILSpy.Docking root.VisibleDockables = CreateList(horizontal); root.DefaultDockable = horizontal; root.ActiveDockable = horizontal; + root.DockCapabilityPolicy = new DockCapabilityPolicy(); return root; } @@ -140,6 +148,7 @@ namespace ILSpy.Docking ToolPaneAlignment.Bottom => Alignment.Bottom, _ => Alignment.Left, }, + DockCapabilityPolicy = new DockCapabilityPolicy(), }; } diff --git a/ILSpy/ViewModels/ContentTabPage.cs b/ILSpy/ViewModels/ContentTabPage.cs index 852a13ced..285d0fc08 100644 --- a/ILSpy/ViewModels/ContentTabPage.cs +++ b/ILSpy/ViewModels/ContentTabPage.cs @@ -21,6 +21,7 @@ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; using Dock.Controls.DeferredContentControl; +using Dock.Model.Core; using ICSharpCode.ILSpyX.TreeView; @@ -35,6 +36,15 @@ namespace ILSpy.ViewModels /// public sealed partial class ContentTabPage : TabPageModel, IDeferredContentPresentation { + public ContentTabPage() + { + // Same reason as ToolPaneModel — Dock's chrome template binds against + // DockCapabilityOverrides.{CanDrag, CanDrop, CanClose}, and the document tab's + // owner.DockCapabilityPolicy.{CanDrag, CanDrop, CanClose}. Without these + // pre-populated, every tab startup logs a [Binding] error per property. + DockCapabilityOverrides = new DockCapabilityOverrides(); + } + // Opt out of Dock's deferred presentation: the inner views are already pre-realised // by the wrapper view, and headless tests can't reach descendants of a control // that's still queued for realisation. diff --git a/ILSpy/ViewModels/ToolPaneModel.cs b/ILSpy/ViewModels/ToolPaneModel.cs index 080bbfdfe..2e125b542 100644 --- a/ILSpy/ViewModels/ToolPaneModel.cs +++ b/ILSpy/ViewModels/ToolPaneModel.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using Dock.Controls.DeferredContentControl; +using Dock.Model.Core; using Dock.Model.Mvvm.Controls; namespace ILSpy.ViewModels @@ -28,9 +29,21 @@ namespace ILSpy.ViewModels /// hundreds of milliseconds between the window painting and the panes appearing. The /// trees + search results are cheap to materialise (lazy loading handles deeper levels) /// so eager realisation is the right tradeoff — same as ContentTabPage. + /// + /// The ctor also pre-populates with a + /// default instance because the Dock theme template binds against + /// ActiveDockable.DockCapabilityOverrides.CanPin / CanClose (and similar) + /// the moment the pane chrome is realised. Without a non-null instance every pane + /// startup logs a [Binding] error per property — adds up to ~30 errors per + /// launch. /// public abstract class ToolPaneModel : Tool, IDeferredContentPresentation { + protected ToolPaneModel() + { + DockCapabilityOverrides = new DockCapabilityOverrides(); + } + bool IDeferredContentPresentation.DeferContentPresentation => false; } } diff --git a/ILSpy/Views/MainWindow.axaml.cs b/ILSpy/Views/MainWindow.axaml.cs index cd17bd323..a389d4bb1 100644 --- a/ILSpy/Views/MainWindow.axaml.cs +++ b/ILSpy/Views/MainWindow.axaml.cs @@ -35,15 +35,26 @@ namespace ILSpy.Views public MainWindow() { + // Parameterless ctor — design-time / preview only. The runtime path uses the + // ImportingConstructor below, which sets DataContext BEFORE inflating XAML so + // that DockControl's Layout binding (and the cascade of Layout.Id, Layout.Title, + // Layout.CanDrag, Layout.CanDrop, Layout.DockGroup template bindings) sees a + // non-null source on first evaluation. Without that ordering, every binding + // throws + logs at startup, which adds up to ~30 errors per launch. + StartupLog.Mark("MainWindow parameterless ctor entered (XAML inflation about to start)"); InitializeComponent(); + StartupLog.Mark("MainWindow parameterless ctor exited (XAML inflation done)"); } [ImportingConstructor] - public MainWindow(MainWindowViewModel viewModel, SettingsService settingsService) : this() + public MainWindow(MainWindowViewModel viewModel, SettingsService settingsService) { StartupLog.Mark("MainWindow ctor entered"); this.settingsService = settingsService; DataContext = viewModel; + StartupLog.Mark("MainWindow XAML inflation about to start (DataContext set)"); + InitializeComponent(); + StartupLog.Mark("MainWindow XAML inflation done"); ApplySessionSettings(settingsService.SessionSettings); Opened += async (_, _) => { StartupLog.Mark("MainWindow.Opened fired");