lepoco/wpfui · error · InvalidOperationException

Could not get window handle.

Error message

Could not get window handle.

What it means

Thrown in ObserveLoadedWindow when new WindowInteropHelper(window).Handle returns IntPtr.Zero, meaning the WPF window has no HWND yet. This path is for windows expected to already be loaded, so a zero handle indicates the caller invoked Observe too early or on an uninitialized window.

Source

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

        {
            ObserveWindowWhenLoaded(window, backdrop, updateAccents);
        }

        if (_observedWindows.Count == 0)
        {
            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 += (_, _) =>
        {

View on GitHub (pinned to ffebacd610)

Solutions

  1. Call Observe from the Window.Loaded event (or use ObserveWindowWhenLoaded) so the HWND exists.
  2. Defer watching until IsLoaded is true: SystemThemeWatcher.Watch(window) which hooks Loaded internally when needed.
  3. Ensure the window is actually shown (Show/ShowDialog) before observing it.

Example fix

// before
SystemThemeWatcher.Observe(window); // in constructor, handle is 0

// after
window.Loaded += (_, _) => SystemThemeWatcher.Observe(window);
Defensive patterns

Strategy: validation

Validate before calling

if (new WindowInteropHelper(window).Handle == IntPtr.Zero) { /* defer to Loaded/SourceInitialized */ }

Type guard

static bool WindowHasHandle(System.Windows.Window w) => w.IsLoaded && new WindowInteropHelper(w).Handle != IntPtr.Zero;

Prevention

When it happens

Trigger: Calling SystemThemeWatcher.Observe (the synchronous path) on a Window that has not completed its first render pass, so WindowInteropHelper.Handle is still zero. The inline ternary expression throws as soon as the handle compares equal to IntPtr.Zero.

Common situations: Watching a window in its constructor, in Window.Initialized (before HWND creation), or a window whose Show has not been called. Also when the visual tree is not yet hooked to a presentation source.

Related errors


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