Browse Source

Kill the Dock binding-error spam at startup

Two fixes covering the ~30 [Binding] errors that fire during MainWindow startup
and contribute to the 900 ms+ Window.Opened → AssemblyListPane ctor gap.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
aabb8b65bd
  1. 15
      ILSpy/AssemblyTree/AssemblyTreeModel.cs
  2. 9
      ILSpy/Docking/ILSpyDockFactory.cs
  3. 10
      ILSpy/ViewModels/ContentTabPage.cs
  4. 13
      ILSpy/ViewModels/ToolPaneModel.cs
  5. 13
      ILSpy/Views/MainWindow.axaml.cs

15
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -291,17 +291,22 @@ namespace ILSpy.AssemblyTree @@ -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 <see cref="LoadedAssembly"/> 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 (<see cref="TreeReady"/>) plus a small visibility cooldown.
/// Gating on <see cref="TreeReady"/> 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.
/// </summary>
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())
{

9
ILSpy/Docking/ILSpyDockFactory.cs

@ -55,6 +55,11 @@ namespace ILSpy.Docking @@ -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 @@ -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 @@ -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 @@ -116,6 +123,7 @@ namespace ILSpy.Docking
root.VisibleDockables = CreateList<IDockable>(horizontal);
root.DefaultDockable = horizontal;
root.ActiveDockable = horizontal;
root.DockCapabilityPolicy = new DockCapabilityPolicy();
return root;
}
@ -140,6 +148,7 @@ namespace ILSpy.Docking @@ -140,6 +148,7 @@ namespace ILSpy.Docking
ToolPaneAlignment.Bottom => Alignment.Bottom,
_ => Alignment.Left,
},
DockCapabilityPolicy = new DockCapabilityPolicy(),
};
}

10
ILSpy/ViewModels/ContentTabPage.cs

@ -21,6 +21,7 @@ using System.ComponentModel; @@ -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 @@ -35,6 +36,15 @@ namespace ILSpy.ViewModels
/// </summary>
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.

13
ILSpy/ViewModels/ToolPaneModel.cs

@ -17,6 +17,7 @@ @@ -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 @@ -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 <c>ContentTabPage</c>.
///
/// The ctor also pre-populates <see cref="Tool.DockCapabilityOverrides"/> with a
/// default instance because the Dock theme template binds against
/// <c>ActiveDockable.DockCapabilityOverrides.CanPin</c> / <c>CanClose</c> (and similar)
/// the moment the pane chrome is realised. Without a non-null instance every pane
/// startup logs a <c>[Binding]</c> error per property — adds up to ~30 errors per
/// launch.
/// </summary>
public abstract class ToolPaneModel : Tool, IDeferredContentPresentation
{
protected ToolPaneModel()
{
DockCapabilityOverrides = new DockCapabilityOverrides();
}
bool IDeferredContentPresentation.DeferContentPresentation => false;
}
}

13
ILSpy/Views/MainWindow.axaml.cs

@ -35,15 +35,26 @@ namespace ILSpy.Views @@ -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");

Loading…
Cancel
Save