lepoco/wpfui · warning · InvalidOperationException

Window handle cannot be empty

Error message

Window handle cannot be empty

What it means

This is the 'Window handle cannot be empty' guard inside ObserveLoadedWindow. It is effectively dead code: the preceding ternary already throws InvalidOperationException when hWnd == IntPtr.Zero, so this if-block can never execute. It documents intent but cannot be the actual cause of a thrown exception in that method.

Source

Thrown at src/Wpf.Ui/Appearance/SystemThemeWatcher.cs:77

        {
            System.Diagnostics.Debug.WriteLine(
                $"INFO | {typeof(SystemThemeWatcher)} changed the app theme on initialization.",
                nameof(SystemThemeWatcher)
            );
            ApplicationThemeManager.ApplySystemTheme(updateAccents);
        }
    }

    private static void ObserveLoadedWindow(Window window, WindowBackdropType backdrop, bool updateAccents)
    {
        IntPtr hWnd =
            (hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero
                ? throw new InvalidOperationException("Could not get window handle.")
                : hWnd;

        if (hWnd == IntPtr.Zero)
        {
            throw new InvalidOperationException("Window handle cannot be empty");
        }

        ObserveLoadedHandle(new ObservedWindow(hWnd, backdrop, updateAccents));
    }

    private static void ObserveWindowWhenLoaded(
        Window window,
        WindowBackdropType backdrop,
        bool updateAccents
    )
    {
        window.Loaded += (_, _) =>
        {
            IntPtr hWnd =
                (hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero
                    ? throw new InvalidOperationException("Could not get window handle.")
                    : hWnd;

View on GitHub (pinned to ffebacd610)

Solutions

  1. Treat error 5 (the ternary throw) as the real source of a zero-handle failure in ObserveLoadedWindow; resolve the underlying too-early-watch issue there.
  2. Optionally remove the redundant if-block to reduce confusion, since it can never fire.
  3. Add an XML doc comment clarifying that the ternary is the authoritative guard.

Example fix

// before
IntPtr hWnd =
    (hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero
        ? throw new InvalidOperationException("Could not get window handle.")
        : hWnd;
if (hWnd == IntPtr.Zero)
{
    throw new InvalidOperationException("Window handle cannot be empty");
}

// after
IntPtr hWnd =
    (hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero
        ? throw new InvalidOperationException("Could not get window handle.")
        : hWnd;
Defensive patterns

Strategy: validation

Validate before calling

// No runtime guard needed; this block is unreachable. Remove it or rely on the ternary guard above.

Prevention

When it happens

Trigger: Unreachable in normal execution because the assignment-ternary throws first whenever the handle is zero. It would only be the active throw site if the ternary expression were removed or refactored.

Common situations: Developers reading the stack trace will not see this line as the throw origin; the real throw is at the ternary (error 5). The duplicate check is a maintenance/complexity smell.

Related errors


AI-assisted analysis of lepoco/wpfui@ffebacd610 (2026-08-13). Data as JSON: /api/errors/51b3e3a64f862ed6. Report an issue: GitHub.