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");