代码之家  ›  专栏  ›  技术社区  ›  Paul Mrozowski

如何在动画完成之前阻止对方法的访问

  •  2
  • Paul Mrozowski  · 技术社区  · 14 年前

    基本上,我要阻止任何其他两个方法调用动画,直到完成第一个动画为止。我看到DoubleAnimation()类触发了一个已完成的事件,但我没有成功地构造任何类型的代码,直到这两个类都完成了才能成功地阻止它们。

    我尝试使用Monitor.Enter在输入该方法时启用私有成员,然后从其中一个动画完成事件中释放锁,但我尝试链接这两个事件(因此在两个事件都完成之前不会释放锁)的尝试没有成功。

        public void AnimateRectangle(Rectangle rect, double newX, double newY)
        {
                var xIsComplete = false;
    
                Duration duration = new Duration(new TimeSpan(0, 0, 0, 1, 350));
                var easing = new ElasticEase() { EasingMode = EasingMode.EaseOut, Oscillations = 1, Springiness = 4 };
                var animateX = new DoubleAnimation();
                var animateY = new DoubleAnimation();
    
                animateX.EasingFunction = easing;
                animateX.Duration = duration;
                animateY.EasingFunction = easing;
                animateY.Duration = duration;
    
                var sb = new Storyboard();
    
                sb.Duration = duration;
                sb.Children.Add(animateX);
                sb.Children.Add(animateY);
    
                Storyboard.SetTarget(animateX, rect);
                Storyboard.SetTargetProperty(animateX, new PropertyPath("(Canvas.Left)"));
                Storyboard.SetTarget(animateY, rect);
                Storyboard.SetTargetProperty(animateY, new PropertyPath("(Canvas.Top)"));
    
                animateX.To = newX;
                animateY.To = newY;
                sb.Begin();
    
        }
    

    编辑(添加更多信息)

    我最初遇到这个问题是因为我从另一个方法调用这个方法(当它处理项目时,它调用了动画)。我注意到这些东西并没有达到我预期的效果。我传入的新X/Y坐标基于项目当前位置,因此,如果在完成之前多次调用它,那么它最终将位于错误的位置。作为测试,我添加了一个只运行一次动画的按钮。成功了。但是,如果我连续多次单击按钮,我会看到与以前相同的行为:项目最终位于错误的位置。

    是的,Silverlight动画似乎在主UI线程上运行。在我尝试的一个测试中,我添加了两个属性,用于标记两个动画是否都已完成。在AnimateRectange()方法中,我在while循环(调用Thread.Sleep)中检查了它们。这个循环从未完成(因此它肯定在同一个线程上)。

    因此,我创建了一个队列来按顺序处理动画:

        private void ProcessAnimationQueue()
        {
            var items = this.m_animationQueue.GetEnumerator();
            while (items.MoveNext())
            {
                while (this.m_isXanimationInProgress || this.m_isYanimationInProgress)
                {
                    System.Threading.Thread.Sleep(100);
                }
    
                var item = items.Current;
                Dispatcher.BeginInvoke(() => this.AnimateRectangle(item.Rect.Rect, item.X, item.Y));                
            }
        }
    

    然后我调用我的初始例程(将动画排队),并在新线程上调用此方法。我看到了同样的结果。

    2 回复  |  直到 14 年前
        1
  •  0
  •   Dan Auclair    14 年前

    据我所知,Silverlight中的所有动画都发生在UI线程上。我猜无论如何只有UI线程在调用这个动画函数,所以我不确定使用锁定是否会有帮助。你真的想阻止整个线程还是仅仅阻止另一个动画开始?

    我建议采取类似的措施:

    private bool isAnimating = false;
    
    public void AnimateRectangle(Rectangle rect, double newX, double newY)
    {
        if (isAnimating)
            return;
    
        // rest of animation code
    
        sb.Completed += (sender, e) =>
        {
            isAnimating = false;
        };
    
        isAnimating = true;
        sb.Begin();
    }
    

        2
  •  0
  •   AnthonyWJones    14 年前

    这个问题真的引起了我极大的兴趣。事实上,我将在我的下一篇博文中包含它。

    AnimateRectangle 您只需要“排队”调用,以便在任何未完成的调用完成其动画后,执行此“排队”调用。如果前一个通话尚未开始,您可能需要将多个通话排队。

    因此,我们需要两件事:-

    1. 一种处理本质上是异步操作的方法( sb.Begin 对于已完成事件),作为顺序操作,一个操作仅在前一个操作完成时开始。
    2. 当一个或多个操作尚未完成时,将附加操作排队的一种方法。

    异步操作服务

    项目1在Silverlight中以无数种不同的方式出现,这是因为有很多东西是异步的。我通过一个简单的异步运行程序解决了这个问题 here . 添加 AsyncOperationService

    异步操作队列

    它的项目2真的引起了我的兴趣。这里的变化是,当一组现有操作正在进行时,需要添加另一组操作。对于一般情况下的解决方案,我们需要一种线程安全的方法来包含另一个操作。

    这是一只秃鹰的骨头 AsyncOperationQueue :-

    public class AsyncOperationQueue
    {
        readonly Queue<AsyncOperation> myQueue = new Queue<AsyncOperation>();
        AsyncOperation myCurrentOp = null;
    
        public void Enqueue(AsyncOperation op)
        {
            bool start = false;
    
            lock (myQueue)
            {
                if (myCurrentOp != null)
                {
                    myQueue.Enqueue(op);
                }
                else
                {
                    myCurrentOp = op;
                    start = true;
                }
            }
    
            if (start)
                DequeueOps().Run(delegate { });
        }
    
        private AsyncOperation GetNextOperation()
        {
            lock (myQueue)
            {
                myCurrentOp = (myQueue.Count > 0) ? myQueue.Dequeue() : null;
                return myCurrentOp;
            }
        }
    
        private IEnumerable<AsyncOperation> DequeueOps()
        {
            AsyncOperation nextOp = myCurrentOp;
            while (nextOp != null)
            {
                yield return nextOp;
                nextOp = GetNextOperation();
            }
        }
    }
    

    首先要做的是转换现有的 垂直角 方法转换为 GetAnimateRectangleOp 返回一个 AsyncOperation

        public AsyncOperation GetAnimateRectangleOp(Rectangle rect, double newX, double newY)
        {
            return (completed) =>
            {
                // Code identical to the body of your original AnimateRectangle method.
                sb.Begin();
    
                sb.Completed += (s, args) => completed(null);
            };
    
        }
    

    我们需要举一个例子 :-

     private AsyncOperationQueue myAnimationQueue = new AsyncOperationQueue();
    

    最后,我们需要重新创建 垂直角 将操作排入队列的步骤:-

     public void AnimateRectangle(Rectangle rect, double newX, double newY)  
     {
         myAnimationQueue.Enqueue(GetAnimateRectangleOp(rect, newX, newY)
     }