stride3d/stride · error · InvalidOperationException

A dependency object must be attached in order to unregister…

Error message

A dependency object must be attached in order to unregister a handler.

What it means

DetachHandler requires the watcher to still be attached to a FrameworkElement; if frameworkElement is null it throws InvalidOperationException 'A dependency object must be attached in order to unregister a handler.' Removing a value-change subscription needs the same live DependencyObject that AddValueChanged was called with. Called by UnregisterValueChangedHander and DetachHandlers.

Solutions

  1. Track attach state and skip per-handler unregister calls once the watcher is detached — Detach/DetachHandlers already performs full cleanup.
  2. Perform unregistering inside the element's Unloaded handler before calling watcher Detach, or rely solely on the watcher's own detach path.
  3. Avoid double-detach by guarding with a bool flag or checking watcher attach state.
  4. Re-attach the watcher to the element before attempting targeted unregister if the element is live again.

Example fix

// before
watcher.Detach();
watcher.UnregisterValueChangedHander(prop, OnChanged); // already detached
// after
watcher.UnregisterValueChangedHander(prop, OnChanged);
watcher.Detach();
Defensive patterns

Strategy: validation

Validate before calling

if (watcher.AssociatedObject == null) { /* already detached; nothing to unregister */ return; }

Type guard

static bool CanUnregisterFromWatcher(DependencyPropertyWatcher w) => w?.AssociatedObject is not null;

Try / catch

try { watcher.UnregisterValueChangedHander(prop, handler); }
catch (InvalidOperationException ex) when (ex.Message.Contains("must be attached")) { /* watcher already detached; safe to ignore */ }

Prevention

When it happens

Trigger: Calling UnregisterValueChangedHander after the watcher was detached (element reference cleared, e.g. by Detach() or after the element unloaded) or before it was ever attached.

Common situations: Double-detach in unload/unloaded handlers; WPF element unloading (page navigation, ItemsControl recycling) followed by explicit per-handler unregister calls; application shutdown paths racing with cleanup code.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/e6d963537e98bee0. Report an issue: GitHub.

Appendix: source

Thrown at sources/presentation/Stride.Core.Presentation.Wpf/Core/DependencyPropertyWatcher.cs:120

        {
            if (property == null) throw new ArgumentNullException(nameof(property));
            if (handler == null) throw new ArgumentNullException(nameof(handler));
            if (frameworkElement == null) throw new InvalidOperationException("A dependency object must be attached in order to register a handler.");

            DependencyPropertyDescriptor descriptor;
            if (!descriptors.TryGetValue(property, out descriptor))
            {
                descriptor = DependencyPropertyDescriptor.FromProperty(property, AssociatedObject.GetType());
                descriptors.Add(property, descriptor);
            }
            descriptor.AddValueChanged(AssociatedObject, handler);
        }

        private void DetachHandler([NotNull] DependencyProperty property, [NotNull] EventHandler handler)
        {
            if (property == null) throw new ArgumentNullException(nameof(property));
            if (handler == null) throw new ArgumentNullException(nameof(handler));
            if (frameworkElement == null) throw new InvalidOperationException("A dependency object must be attached in order to unregister a handler.");

            DependencyPropertyDescriptor descriptor;
            if (!descriptors.TryGetValue(property, out descriptor))
            {
                throw new InvalidOperationException("No handler was previously registered for this dependency property.");
            }
            descriptor.RemoveValueChanged(AssociatedObject, handler);
        }

        private void ElementLoaded(object sender, RoutedEventArgs e)
        {
            AttachHandlers();
        }

        private void ElementUnloaded(object sender, RoutedEventArgs e)
        {
            DetachHandlers();
        }

View on GitHub (pinned to 96fad776d2)