You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF自定义IEasingFunction不继承Freezable为何触发冻结异常?

WPF自定义缓动函数继承Freezable的原因说明

问题背景

我正在尝试实现一个自定义的IEasingFunction,它本质是另一个IEasingFunction的包装器,会在动画的开头和结尾增加停顿效果。我不希望它继承自EasingFunctionBase,因为给PausingEasingFunction设置EasingMode没有实际意义:无论使用什么缓动模式,停顿效果都是一致的,被包装的缓动函数可以自行指定其缓动模式。

初始代码

PausingEasingFunction.cs

public class PausingEasingFunction : DependencyObject, IEasingFunction
{
    public static readonly DependencyProperty WrappedEasingFunctionProperty = DependencyProperty.Register(nameof(WrappedEasingFunction), typeof(IEasingFunction), typeof(PausingEasingFunction));

    public static readonly DependencyProperty StartPauseProperty = DependencyProperty.Register(nameof(StartPause), typeof(double), typeof(PausingEasingFunction));

    public static readonly DependencyProperty EndPauseProperty = DependencyProperty.Register(nameof(EndPause), typeof(double), typeof(PausingEasingFunction));

    public IEasingFunction WrappedEasingFunction
    {
        get => (IEasingFunction)GetValue(WrappedEasingFunctionProperty);
        set => SetValue(WrappedEasingFunctionProperty, value);
    }

    public double StartPause
    {
        get => (double)GetValue(StartPauseProperty);
        set => SetValue(StartPauseProperty, value);
    }

    public double EndPause
    {
        get => (double)GetValue(EndPauseProperty);
        set => SetValue(EndPauseProperty, value);
    }

    public double Ease(double normalizedTime)
    {
        if (normalizedTime < StartPause)
        {
            return 0;
        }
        else if (normalizedTime <= 1 - EndPause)
        {
            if (WrappedEasingFunction == null)
            {
                return (normalizedTime - StartPause) / (1 - StartPause - EndPause);
            }
            else
            {
                return WrappedEasingFunction.Ease((normalizedTime - StartPause) / (1 - StartPause - EndPause));
            }
        }
        else
        {
            return 1;
        }
    }
}

XAML使用代码(local:是指向PausingEasingFunction所在clr命名空间的xmlns前缀):

<TextBlock Text="Hello I am some animated text!">
    <TextBlock.RenderTransform>
        <TranslateTransform X="0" x:Name="transform"/>
    </TextBlock.RenderTransform>
    <TextBlock.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <BeginStoryboard.Storyboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="transform" RepeatBehavior="Forever" From="0" To="100" Duration="0:0:10">
                            <DoubleAnimation.EasingFunction>
                                <local:PausingEasingFunction>
                                    <local:PausingEasingFunction.WrappedEasingFunction>
                                        <SineEase EasingMode="EaseInOut"/>
                                    </local:PausingEasingFunction.WrappedEasingFunction>
                                </local:PausingEasingFunction>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard.Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </TextBlock.Triggers>
</TextBlock>

问题现象

运行代码会抛出InvalidOperationException,提示Freezable无法冻结。修改PausingEasingFunction.cs让其继承自Freezable而非DependencyObject,并实现CreateInstanceCore方法后异常消失,新增代码如下:

protected override Freezable CreateInstanceCore()
{
    return new PausingEasingFunction();
}

问题解答

  • 这不是WPF的Bug,属于WPF动画系统的设计特性,你的操作没有错误。
  • WPF的动画相关对象(包括Storyboard、各类时间线、动画类)本身都是Freezable类型,为了优化运行性能,动画首次启动前WPF会尝试冻结整个动画对象树中的所有可冻结对象,其中就包括缓动函数属性值。
  • 初始实现中你的自定义缓动函数继承自DependencyObject,不属于Freezable类型,WPF的冻结逻辑遍历到该对象时无法完成冻结操作,就会抛出对应的异常。
  • 只要你将自定义类型作为动画的EasingFunction属性值使用,就必须让其继承自Freezable,这是WPF动画系统的硬性要求。你不需要继承EasingFunctionBase,直接继承Freezable实现IEasingFunction接口即可,你修改后的写法是符合WPF规范的。

内容的提问来源于stack exchange,提问作者nalka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 09:15:01