stride3d/stride · error · InvalidOperationException

No handler was previously registered for this dependency pro

Error message

No handler was previously registered for this dependency property.

What it means

DetachHandler looks up the DependencyPropertyDescriptor for the given property in the watcher's descriptor cache; if no descriptor exists, no handler was ever attached for that property and it throws InvalidOperationException 'No handler was previously registered for this dependency property.' The cache is populated only by AttachHandler, so a miss means no prior registration. Called by UnregisterValueChangedHander and DetachHandlers.

Solutions

  1. Only call UnregisterValueChangedHander for properties you previously registered on the same watcher instance.
  2. Use the watcher's Detach/DetachHandlers bulk cleanup instead of per-property removal when tearing down.
  3. Ensure the exact same DependencyProperty instance is used for attach and detach (share a static field).
  4. Track registered properties in caller code and iterate that set during cleanup.
  5. If unregistering may be redundant, wrap the call in a try-catch for InvalidOperationException and ignore this specific case.

Example fix

// before
watcher.UnregisterValueChangedHander(SomeControl.VisibilityProperty, OnChanged); // never registered
// after
watcher.RegisterValueChangedHandler(SomeControl.VisibilityProperty, OnChanged);
// ... later, same watcher instance:
watcher.UnregisterValueChangedHander(SomeControl.VisibilityProperty, OnChanged);
Defensive patterns

Strategy: try-catch

Validate before calling

if (!registeredProperties.Contains(property)) { log.Warn("Property was never registered on this watcher"); return; }

Type guard

static bool WasRegistered(ISet<DependencyProperty> registered, DependencyProperty p) => registered?.Contains(p) == true;

Try / catch

try { watcher.UnregisterValueChangedHander(prop, handler); }
catch (InvalidOperationException ex) when (ex.Message.Contains("No handler was previously registered")) { /* idempotent cleanup: ignore */ }

Prevention

When it happens

Trigger: Unregistering a handler for a property that was never registered; unregistering with a different DependencyProperty instance than the one used at registration; detaching twice (DetachHandlers already removed descriptors); registering on one watcher instance and unregistering on another.

Common situations: Cleanup code that unconditionally unregisters all properties including ones registration skipped; mismatched property constants between attach and detach code paths (e.g. TextBox.TextProperty vs a custom property); re-creating the watcher on reload and trying to unregister against the new instance.

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/0423a61c00da975d. Report an issue: GitHub.

Appendix: source

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

            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)