可见性变化时的动画:该属性已被“FrameworkElement”注册。

3
我正在使用这段代码来在更改可见性时设置动画。
public class VisibilityAnimation : DependencyObject
{
    public enum AnimationType
    {
        None,
        Fade
    }

    private const int AnimationDuration = 1000;

    private static readonly Dictionary<FrameworkElement, bool> _hookedElements =
        new Dictionary<FrameworkElement, bool>();

    public static AnimationType GetAnimationType(DependencyObject obj)
    {
        return (AnimationType)obj.GetValue(AnimationTypeProperty);
    }

    public static void SetAnimationType(DependencyObject obj, AnimationType value)
    {
        obj.SetValue(AnimationTypeProperty, value);
    }

    public static readonly DependencyProperty AnimationTypeProperty =
        DependencyProperty.RegisterAttached(
            "AnimationType",
            typeof(AnimationType),
            typeof(VisibilityAnimation),
            new FrameworkPropertyMetadata(AnimationType.None,
                new PropertyChangedCallback(OnAnimationTypePropertyChanged)));
    private static void OnAnimationTypePropertyChanged(
        DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement frameworkElement = dependencyObject as FrameworkElement;

        if (frameworkElement == null)
        {
            return;
        }

        // If AnimationType is set to True on this framework element, 
        if (GetAnimationType(frameworkElement) != AnimationType.None)
        {
            // Add this framework element to hooked list
            HookVisibilityChanges(frameworkElement);
        }
        else
        {
            // Otherwise, remove it from the hooked list
            UnHookVisibilityChanges(frameworkElement);
        }
    }
    private static void HookVisibilityChanges(FrameworkElement frameworkElement)
    {
        _hookedElements.Add(frameworkElement, false);
    }
    private static void UnHookVisibilityChanges(FrameworkElement frameworkElement)
    {
        if (_hookedElements.ContainsKey(frameworkElement))
        {
            _hookedElements.Remove(frameworkElement);
        }
    }
    static VisibilityAnimation()
    {
        // Here we "register" on Visibility property "before change" event
        UIElement.VisibilityProperty.AddOwner(
            typeof(FrameworkElement),
            new FrameworkPropertyMetadata(
                Visibility.Visible,
                VisibilityChanged,
                CoerceVisibility));

    }
    private static void VisibilityChanged(
        DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs e)
    {
        // Ignore
    }
    private static object CoerceVisibility(
        DependencyObject dependencyObject,
        object baseValue)
    {
        // Make sure object is a framework element
        FrameworkElement frameworkElement = dependencyObject as FrameworkElement;
        if (frameworkElement == null)
        {
            return baseValue;
        }

        // Cast to type safe value
        Visibility visibility = (Visibility)baseValue;

        // If Visibility value hasn't change, do nothing.
        // This can happen if the Visibility property is set using data binding 
        // and the binding source has changed but the new visibility value 
        // hasn't changed.
        if (visibility == frameworkElement.Visibility || visibility == Visibility.Collapsed) //Aggiungo da cri..x fare l'effetto solo sul fade in
        {
            return baseValue;
        }

        // If element is not hooked by our attached property, stop here
        if (!IsHookedElement(frameworkElement))
        {
            return baseValue;
        }

        // Update animation flag
        // If animation already started, don't restart it (otherwise, infinite loop)
        if (UpdateAnimationStartedFlag(frameworkElement))
        {
            return baseValue;
        }

        // If we get here, it means we have to start fade in or fade out animation. 
        // In any case return value of this method will be Visibility.Visible, 
        // to allow the animation.
        DoubleAnimation doubleAnimation = new DoubleAnimation
        {
            Duration = new Duration(TimeSpan.FromMilliseconds(AnimationDuration))
        };

        // When animation completes, set the visibility value to the requested 
        // value (baseValue)
        doubleAnimation.Completed += (sender, eventArgs) =>
        {
            if (visibility == Visibility.Visible)
            {
                // In case we change into Visibility.Visible, the correct value 
                // is already set, so just update the animation started flag
                UpdateAnimationStartedFlag(frameworkElement);
            }
            else
            {
                // This will trigger value coercion again 
                // but UpdateAnimationStartedFlag() function will reture true 
                // this time, thus animation will not be triggered. 
                if (BindingOperations.IsDataBound(frameworkElement,
                    UIElement.VisibilityProperty))
                {
                    // Set visiblity using bounded value
                    Binding bindingValue =
                        BindingOperations.GetBinding(frameworkElement,
                            UIElement.VisibilityProperty);
                    BindingOperations.SetBinding(frameworkElement,
                        UIElement.VisibilityProperty, bindingValue);
                }
                else
                {
                    // No binding, just assign the value
                    frameworkElement.Visibility = visibility;
                }
            }
        };

        if (visibility == Visibility.Collapsed || visibility == Visibility.Hidden)
        {
            // Fade out by animating opacity
            doubleAnimation.From = 1.0;
            doubleAnimation.To = 0.0;
        }
        else
        {
            // Fade in by animating opacity
            doubleAnimation.From = 0.0;
            doubleAnimation.To = 1.0;
        }

        // Start animation
        frameworkElement.BeginAnimation(UIElement.OpacityProperty, doubleAnimation);

        // Make sure the element remains visible during the animation
        // The original requested value will be set in the completed event of 
        // the animation
        return Visibility.Visible;
    }
    private static bool IsHookedElement(FrameworkElement frameworkElement)
    {
        return _hookedElements.ContainsKey(frameworkElement);
    }
    private static bool UpdateAnimationStartedFlag(FrameworkElement frameworkElement)
    {
        bool animationStarted = (bool)_hookedElements[frameworkElement];
        _hookedElements[frameworkElement] = !animationStarted;

        return animationStarted;
    }

在xaml中,我需要设置:VisibilityAnimation.AnimationType="Fade"
动画效果很好,但问题是标题中出现了错误。
我该如何修复这个问题?
Stack Overflow要求更多细节才能插入此代码,但仅有以上内容,请帮忙解决。

这个问题中,调度计时器会不会起作用?因为我在Windows Mobile 8中使用过它。并且可以使用不透明度(来控制可见性)来创建自己的动画。 - gayan1991
你是在寻找基于可见性改变而淡入/淡出的效果吗?还是你关心的更多? - pushpraj
我想了解如何修复这个错误,但我最想做的是淡入效果...如果你有其他可用的替代方案,我会很感激。 - Atomico
你是否只能使用 Visibility?如果使用另一个触发器,比如 FadeHelper.Visibility,你是否可以接受? - pushpraj
2个回答

4
根据MSDN (http://msdn.microsoft.com/en-us/library/ms754209%28v=vs.110%29.aspx),OverrideMetadata(与AddOwner相关)只应从其属性元数据被覆盖的类型的静态构造函数中调用。我认为这个规则对于Dependency Property的AddOwner方法也是适用的。
顺便问一下,您真的需要这样一个应用程序范围的钩子吗?根据您拥有的代码,看起来您真正需要的是FrameworkElement分配给您的附加属性的可见性更改通知。您可以通过以下方式实现此目的。 DependencyPropertyDescriptor.FromProperty(UIElement.VisibilityProperty, typeof(FrameworkElement)).AddValueChange(frameworkElement, callback)
但是,由于您正在寻找一个用于FrameworkElements的Visibility核心值回调的应用程序范围的钩子,因此您可以像这样做。
    static VisibilityAnimation()
    {
        // Here we "register" on Visibility property "before change" event
        var desc = DependencyPropertyDescriptor.FromProperty(UIElement.VisibilityProperty, typeof(FrameworkElement));
        desc.DesignerCoerceValueCallback += CoerceVisibility;

顺便提一下,这个属性附属的FrameworkElements很可能不会被垃圾回收,因为它们被保存在_hookedElements字典中,直到AnimationType属性更改为AnimationType.None。

它可以工作!谢谢!顺便说一句,从你的话中我理解到这种策略不是很好?在可见性改变时有没有淡入效果的替代方案? - Atomico
问题在于:UnHookVisibilityChanges(frameworkElement)是否在实践中被调用?如果没有,您是否能够确保它将被调用?如果两个问题的答案都是否,则您可能需要使用WeakDictionary,例如http://blogs.msdn.com/b/nicholg/archive/2006/06/04/617466.aspx来避免内存泄漏。 - dharshana jagoda

2

在您的静态构造函数中,将所有者类型更改为您的VisibilityAnimation类。

static VisibilityAnimation()
{
    // Here we "register" on Visibility property "before change" event
    UIElement.VisibilityProperty.AddOwner(
        typeof(VisibilityAnimation),
        new FrameworkPropertyMetadata(
            Visibility.Visible,
            VisibilityChanged,
            CoerceVisibility));

}

之前尝试过但是没有成功。现在在XAML中出现了一个错误,指出AnimationType需要从DependenceObject派生。 - Atomico
@Atomico,我没有收到任何错误信息,也许你可以提供一些XAML或代码来展示你如何使用VisibilityAnimation - Roel van Westerop
你看到这个动画了吗?现在它没有报错,但是却没有展示任何动画。 - Atomico
请问您能否粘贴一下您用于更改可见性的代码? - Atomico
不,当我在按钮单击事件处理程序中切换可见性时,它仍然对我有效。 - Roel van Westerop
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接