dotnet/wpf · error · InvalidOperationException

SR.Format(SR.Storyboard_ComplexPathNotSupported…

Error message

SR.Format(SR.Storyboard_ComplexPathNotSupported, targetObject.GetType().ToString())

What it means

Thrown by VerifyComplexPathSupport when a storyboard uses a complex property path on a target object type that does not support it. Only specific types (e.g. those hooked into Storyboard.GetComplexPathValue, like certain Freezable/attached-property scenarios) accept complex paths; anything else is rejected rather than silently ignored.

Solutions

  1. Replace the complex path with a direct property target: set Storyboard.TargetName/TargetProperty to the element and the DependencyProperty directly.
  2. Break the animation into multiple storyboards, each targeting a single hop in the path on a supported type.
  3. If intermediate values must be animated, animate the intermediate object directly (give it a name or reference it as the animation target).

Example fix

// before
Storyboard.SetTargetProperty(sb, new PropertyPath("(0).(1)", new object[] { ... })); // target type doesn't support complex paths
// after
Storyboard.SetTargetName(sb, "myRect");
Storyboard.SetTargetProperty(sb, new PropertyPath("Opacity"));
Defensive patterns

Strategy: validation

Validate before calling

// Only use complex paths on supported targets
if (path.Path.Contains("(") && !(targetObject is Freezable))
    throw new NotSupportedException("Complex path not supported on " + targetObject.GetType());

Type guard

bool SupportsComplexPath(object target) => target is Freezable || target is FrameworkElement && !target.GetType().IsSealed && false; // prefer: keep paths simple for non-Freezables

Try / catch

try { storyboard.Begin(target, true); }
catch (InvalidOperationException ex) when (ex.Message.Contains("complex path")) { storyboard.Begin(simpleTarget, true); }

Prevention

When it happens

Trigger: Storyboard.TargetProperty set to a complex path string while the resolved targetObject's type is not one of the types that call into Storyboard.GetComplexPathValue; ProcessComplexPath then invokes VerifyComplexPathSupport which throws.

Common situations: Copying a multi-segment property path (like '(0).(1)'-style attached/complex paths) onto an arbitrary custom control; animating a POCO wrapper object; WPF-to-Silverlight-style path syntax that is unsupported on the target type.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/bbe44f91153ba6be. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Media/Animation/Storyboard.cs:945

    /// </summary>
    private void VerifyComplexPathSupport( DependencyObject targetObject )
    {
        if(targetObject is FrameworkElement)
        {
            // FrameworkElement and derived types are supported.
            return;
        }

        if(targetObject is FrameworkContentElement)
        {
            // FrameworkContentElement and derived types are supported.
            return;
        }

        // ... and anything else that knows to call into Storyboard.GetComplexPathValue.

        // Otherwise - throw.
        throw new InvalidOperationException(SR.Format(SR.Storyboard_ComplexPathNotSupported, targetObject.GetType().ToString()));
    }

    /// <summary>
    ///     Check to see if there is a complex path that started with the
    /// given target object and property.  If so, process the complex path
    /// information and return the results.
    /// </summary>
    internal static void GetComplexPathValue(
            DependencyObject targetObject,
            DependencyProperty targetProperty,
        ref EffectiveValueEntry entry, 
            PropertyMetadata metadata)
    {
        CloneCacheEntry cacheEntry = GetComplexPathClone(targetObject, targetProperty);

        if (cacheEntry != null)
        {
            object baseValue = entry.Value;

View on GitHub (pinned to 81131a70a4)