WPF行为+故事板动画= AssociatedObject为空

时间:2014-02-07 19:23:31

标签: c# wpf xaml

WPF用户,

我目前正试图解决与WPF Blend行为相关的问题。我发现了一个我想在WPF应用程序中使用的行为,但代码最初是为Silverlight编写的(http://www.sharpgis.net/post/2009/08/11/Silverlight-Behaviors-Triggers-and-Actions.aspx)。

我遇到的问题是,当动画开始并且依赖属性OffsetMediatorProperty发生变化时,我进入方法OnOffsetMediatorPropertyChangedAssociatedObjectnull。此外,它看起来似乎所有字段都是null

我正在使用的行为:

public class MouseScrollViewer : Behavior<ScrollViewer>
{
    double target = 0;
    int direction = 0;
    private Storyboard storyboard;
    private DoubleAnimation animation;

    protected override void OnAttached()
    {
        base.OnAttached();

        CreateStoryBoard();

        AssociatedObject.PreviewMouseWheel += AssociatedObject_MouseWheel;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.PreviewMouseWheel -= AssociatedObject_MouseWheel;
        storyboard = null;
        base.OnDetaching();
    }

    private void AssociatedObject_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        if (Animate(-Math.Sign(e.Delta) * ScrollAmount))
        {
            e.Handled = true;
        }
    }

    private bool Animate(double offset)
    {
        storyboard.Pause();

        if (Math.Sign(offset) != direction)
        {
            target = AssociatedObject.VerticalOffset;
            direction = Math.Sign(offset);
        }

        target += offset;
        target = Math.Max(Math.Min(target, AssociatedObject.ScrollableHeight), 0);

        animation.To = target;
        animation.From = AssociatedObject.VerticalOffset;

        if (animation.From != animation.To)
        {
            storyboard.Begin();
            return true;
        }

        return false;
    }

    private void CreateStoryBoard()
    {
        storyboard = new Storyboard();
        animation = new DoubleAnimation
        {
            Duration = TimeSpan.FromSeconds(.5),
            EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseOut }
        };

        Storyboard.SetTarget(animation, this);
        Storyboard.SetTargetProperty(animation, new PropertyPath(OffsetMediatorProperty));

        storyboard.Children.Add(animation);
        storyboard.Completed += (s, e) => { direction = 0; };
    }

    internal double OffsetMediator
    {
        get { return (double)GetValue(OffsetMediatorProperty); }
        set { SetValue(OffsetMediatorProperty, value); }
    }

    internal static readonly DependencyProperty OffsetMediatorProperty =
        DependencyProperty.Register("OffsetMediator", typeof(double), typeof(MouseScrollViewer), new PropertyMetadata(0.0, OnOffsetMediatorPropertyChanged));

    private static void OnOffsetMediatorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MouseScrollViewer msv = d as MouseScrollViewer;
        if (msv != null && msv.AssociatedObject != null)
        {
            msv.AssociatedObject.ScrollToVerticalOffset((double)e.NewValue);
        }
    }

    public double ScrollAmount
    {
        get { return (double)GetValue(ScrollAmountProperty); }
        set { SetValue(ScrollAmountProperty, value); }
    }

    public static readonly DependencyProperty ScrollAmountProperty =
        DependencyProperty.Register("ScrollAmount", typeof(double), typeof(MouseScrollViewer), new PropertyMetadata(50.0));
}

用法:

<ScrollViewer>
        <i:Interaction.Behaviors>
            <local:MouseScrollViewer x:Name="ScrollViewer" />
        </i:Interaction.Behaviors>

        <ItemsControl ItemsSource="{Binding}"                              
                      FontSize="32">

        </ItemsControl>
    </ScrollViewer>

看起来这种行为在Silverlight演示中有效,但在WPF应用程序中却无效。我真的希望你们中的一些人能够解释我为什么会这样,希望能帮助我解决这个问题。

1 个答案:

答案 0 :(得分:2)

当使用Begin()运行storyboard时,它会在内部尝试访问freezable动画对象,在您的情况下,它是类MouseScrollViewer的实例对象,因此它最终会克隆一个动画实例并且最终你的MosueScrollViewer

因此,实际问题是 AssociatedObject为null,因为动画是在另一个MouseScrollViewer实例上完成的,而不是在您的实际实例上


有两种解决方法:

首先在动画对象上调用animation.Freeze(),以便不创建新实例,但是这种方法的问题是它只能在第一次工作,第二次会失败, 冻结对象属性可以不得修改


第二种解决方法是,当您需要代码中的动画时,根本不需要故事板。您可以 直接在当前实例上执行动画 。请看下面您需要做的更改:

首先将您的代码完全删除storyboard

第二次CreateStoryBoard()修改为CreateAnimation()

private void CreateAnimation()
{
   animation = new DoubleAnimation
   {
      Duration = TimeSpan.FromSeconds(.5),
      EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseOut }
   };

   Storyboard.SetTarget(animation, this);
   Storyboard.SetTargetProperty(animation, new 
                               PropertyPath(OffsetMediatorProperty));

   animation.Completed += (s, e) => { direction = 0; };
}

Animate()方法应该是这样的:

private bool Animate(double offset)
{
   if (Math.Sign(offset) != direction)
   {
      target = AssociatedObject.VerticalOffset;
      direction = Math.Sign(offset);
   }

   target += offset;
   target = Math.Max(Math.Min(target, AssociatedObject.ScrollableHeight), 0);

   animation.To = target;
   animation.From = AssociatedObject.VerticalOffset;

   if (animation.From != animation.To)
   {
      this.BeginAnimation(OffsetMediatorProperty, animation); <-- HERE
      return true;
   }

   return false;
}