Silverlight数学引擎(5)——动画演示

以上四节,我们实现了一个混合表达式的解析和求值过程,效果很不直观,所以这节我们做一个简单的动画来演示整个过程吧,以显得工作不那么虎头蛇尾:)

这里有两个过程需要演示,解析过程求值过程

先说解析过程吧,我们已经按解析的顺序把Nodes存入了CalculatorFoundNodes里面,常量和者变量(x)是最底层的Node,所以没有依赖到更底层的Node,其他的Node则依赖于其他的一些Nodes。如果把这种依赖关系画成图,则是一个树形结构,树的根节点就是我们最后解析出来的Node。

树的类型可以根据拥有最大依赖节点数的节点确定,如果只有二元的,则树是二叉树,如果用到了多个参数的函数如(Max(1,2,3))则是多叉树了。不过可以确定,大部分情况下是二叉树啦,而且很可能不是满二叉树。

下面我们要做的工作就是将树以图形的方式显示出来,这就需要对每个节点计算其位置,然后将父子节点用线连起来,使得显示出来的是一个树形结构,位置的计算逻辑有很多种,为了不麻烦,我们用一种简单的方法——使用满树的结构模式(因为满树比这种缺胳膊少腿的树有规则啦,是对称的,便于计算排列位置),然后计算当前树中各个节点在满树中的位置,然后对号入座就OK啦。

先来看一下树的定义(具体的实现代码比较长而且逻辑简单,就不贴出来了,大家还是看源文件吧),我们还是采用自由布局的控件Canvas来作为TreeTreeNode的容器:

    public class Tree : Canvas
{ 
double NodeSize = 50;
        public int TreeType { get; private set; }
        private INode RootNode;
        public TextBlock Expression;
        private List<INode> Nodes;//按解析顺序排列的List
}

    public class TreeNode: Canvas 
{        public const int ZIndex = 200;
        public INode Node { get; private set; }
        private Tree Tree { get { return Parent as Tree; }}
        public Point Center { get; set; }
        public TextBlock Text;
        public Ellipse Shape;
}

定义很简单没有什么好解释的了,其实动画也是非常简单的动画,如元素移动或者变色,我们关注的是过程的顺序:

  1. 解析的顺序:我们已经存入了FoundList
  2. 求值的顺序:我们在NodeBase中加入一个静态属性CalculatedNodes来记录它,因为GetValue()是递归调用的,我们可以再GetValue() return 之前将Node加入CalculatedNodes列表即可.
            public override double GetValue()
            {
                var d= 0 - Node.GetValue();
                Record();//将Node加入CalculatedNodes列表
                return d;
            }

    虽然都是简单的动画,但要写的机械性的代码确实不少,对于避免机械,诸位有何良策?经过寡人三思,最终写出了几个通用的重复性事件处理方法,省去了不少机械劳动呢,拿出来Show一下吧:

        //单个元素的连续事件执行,如将Canvas从(0,0)移动到(100,100),
        public static void RunCommand<T>(double interval, int times, T obj, 
            Action<T> cmd, //主过程
            Action<T> startCmd, //初始化过程
            Action<T> overCmd//收尾过程
            )
        {
            var timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(interval)};
            var iTimerCount = times;
            timer.Tick += (s, e) =>
                              {
                                  if (iTimerCount <= 0)
                                  {
                                      timer.Stop();
                                      Run(overCmd, obj);
                                      return;
                                  }
                                  Run(cmd, obj);
                                  iTimerCount--;
                              };
            Run(startCmd, obj);
            timer.Start();
        }

        //批量元素的连续事件处理,如将10个Canvas依次从(0,0)移动到各自的目标位置,
        public static void RunCommand<T>(double interval, List<T> list, 
            Action<T> cmd, Action startCmd, Action overCmd)
        {
            var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(interval) };
            int iIndex = 0;
            timer.Tick += (s, e) =>
            {
                if (iIndex >= list.Count)
                {
                    timer.Stop();
                    Run(overCmd);
                    return;
                }
                cmd(list[iIndex++]);
            };
            Run(startCmd);
            Thread.Sleep(100);
            timer.Start();
        }

来看看它们的用法:PlayWithShapeMoving()实现了将一个节点从解析的位置移动到它在树中的位置,属于单个元素的重复性事件处理,Play()则实现了对所有节点的动画演示,其中就包含了PlayWithShapeMoving(),而且将PlayWithGetValues()作为“Action overCmd”传入,保证了所有节点都各就各位后才开始求值过程的演示,是不是挺简单的啊:

        //单个节点运动
        private void PlayWithShapeMoving(TreeNode shape, Point from, Point to)
        {
            const int times = 8;
            double offsetX = (to.X - from.X)/times;
            double offsetY = (to.Y - from.Y)/times;
            EventUtils.RunCommand(100, times, shape,
                                  s => //动画主过程:重复times次
                                      {
                                          var p = s.Center();
                                          s.CenterTo(new Point(p.X + offsetX, p.Y + offsetY));
                                      },
                                  s => //初始化过程:
                                      {
                                          shape.CenterTo(from);//移动到开始位置
                                          shape.Visibility = Visibility.Visible;
                                          Expression.Text = shape.Node.Expression;
                                      },
                                  s => //收尾动作:画线连接父子节点
                                  s.DrawLinesToChilds());
        }

        //动画演示解析和取值过程
        public void Play()
        {
            var shapes = Children.OfType<TreeNode>().ToList();
            shapes.ForEach(s => s.Visibility = Visibility.Collapsed);

            var startPosition = Expression.Center();
            var shapeList = //根据解析顺序排序Shapes
                Nodes.Select(node => shapes.FirstOrDefault(n => n.Node == node)).ToList();

            EventUtils.RunCommand(1000, shapeList,
                                  s => PlayWithShapeMoving(s, startPosition, s.Center), //动画演示解析过程
                                  null, //初始化(无)
                                  PlayWithGetValues //动画演示取值过程
                );
        }

来看看运行截图吧:

【源代码和演示地址】

好啦,表达式的解析就到此为止了,虽然我们还有很多事情没有做,例如对变量有效区间的计算、非法表达式的检测、性能测试等等…,这是都是很重要需要完善的地方,以后会根据需要加以完善!

 下一部分我们将开启新的功能模块——实现几何作图功能,因为涉及到UI编程就会和很多的事件打交道,几何作图也不是一篇就能讲清楚的!下一节我们就从简单的尺规作图三大元素之一的“点”开始吧,然后从点到线从线到面,来探索我们奥妙无穷的几何宇宙!

posted @ 2012-11-26 16:57  地月银光  阅读(1750)  评论(1)    收藏  举报