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
- Only call UnregisterValueChangedHander for properties you previously registered on the same watcher instance.
- Use the watcher's Detach/DetachHandlers bulk cleanup instead of per-property removal when tearing down.
- Ensure the exact same DependencyProperty instance is used for attach and detach (share a static field).
- Track registered properties in caller code and iterate that set during cleanup.
- 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
- Maintain a set of registered (property, handler) pairs in caller code.
- Always use the same watcher instance for register and unregister.
- Use Detach/DetachHandlers for teardown instead of per-property removal.
- Share static DependencyProperty fields across attach/detach code paths.
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
- Can't change the parent of this folder
- A dependency object must be attached in order to register a
- A dependency object must be attached in order to unregister
- This BehaviorCollection has already been attached to a depen
- The given {window} is not registered as a clipboard listener
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)