如何使用Thread.Sleep为二叉树设置动画?

时间:2012-04-06 17:49:56

标签: c# winforms animation binary-tree

我一直致力于一个可视化输出二叉树内容的程序(依次由我自己编写的类表示)。我希望在此程序中包含的最后一个功能是树的后序,顺序和预订构造的动画。

事实证明这比我想象的更具挑战性。这是最初的绘图方法:

private void DrawNode(int x, int y, BinaryTreeNode<T> node, int nodeLevel, int maxDepth, int connectX = -1, int connectY = -1, )
    {
        //calculate distance between the node's children
        int distance = CalculateDistance(nodeLevel, maxDepth);

        //draw the node at the specified coordinate
        node.Draw(x, y, this.device);

        if (node.Left != null)
        {
            DrawNode(x - distance / 2, y + 50, node.Left, nodeLevel + 1, maxDepth, x, y, node);
        }
        if (node.Right != null)
        {
            DrawNode(x + distance / 2, y + 50, node.Right, nodeLevel + 1, maxDepth, x, y, node);
        }

        //connect the node to its parent
        if ((connectX != -1) && (connectY != -1))
        {
            node.Connect(connectX, connectY, device);
        }

        this.display.Image = surface;
    }

我最初的想法是简单地将Thread.Sleep(1000)放在前两个if子句的每一个中 - 我真正需要做的就是在每次绘制节点之前暂停执行程序1秒。

我意识到Sleep方法阻止了绘图代码的执行,所以我放弃了那个方法..然后我尝试使用Timers,但发现在处理树时难以置信。

我的目标是简单地找到一种暂停程序执行的方法,而不会破坏GUI的响应能力,也不会使代码过于复杂。

任何帮助将不胜感激:)。

编辑:一些可能相关的信息:程序在Winforms上运行,所有图形都通过GDI +处理。如果您需要任何其他信息,请询问:)

编辑:对于SLaks,

//draw the node's children
        if (drawChildren)
        {
            if (node.Left != null)
            {
                if (this.timer2.Enabled)
                {
                    this.timer2.Stop();
                }
                if (!this.timer1.Enabled)
                {
                    this.timer1.Start();
                }
                this.count1++;
                this.timer1.Tick += (object source, EventArgs e) =>
                {
                    this.count1--;
                    DrawNode(x - distance / 2, y + 50, node.Left, nodeLevel + 1, maxDepth, x, y, node);
                    if (this.count1 == 0)
                    {
                        this.timer1.Stop();
                    }
                };
            }
            else
            {
                this.timer1.Stop();
                this.timer2.Start();
            }
            if (node.Right != null)
            {
                this.count2++;
                this.timer2.Tick += (object source, EventArgs e) =>
                {
                    this.count2--;
                    DrawNode(x + distance / 2, y + 50, node.Right, nodeLevel + 1, maxDepth, x, y, node);
                    if (this.count2 == 0)
                    {
                        this.timer2.Stop();
                    }
                };
            }
        }

4 个答案:

答案 0 :(得分:4)

使用计时器并设置适当的更新间隔。在Tick事件中执行下一步绘制并显示它。

答案 1 :(得分:2)

首先,给自己写一个DrawNodesForLevel(int level)函数。然后从顶层开始,启动计时器,每次滴答时,用适当的级别调用DrawNodesForLevel(),然后递增级别。当你走到尽头时,停止计时器。

编辑:更新时理解您希望在每个节点之间暂停,而不是在每个级别之间暂停。

将函数中的变量移动到自己的DrawNodeState类,并在调用DrawNode()时传递该类的实例。然后,不要让DrawNode()调用自己,让DrawNode()启动一个计时器(也是DrawNodeState类的一部分)。当该计时器滴答时,tick函数调用DrawNode()并将状态结构传递给它。

状态结构也必须跟踪它是否最后绘制了左或右节点,因此它可以接下来绘制相应的节点。

答案 2 :(得分:1)

将您的代码分成两部分 - 一部分遍历树,另一部分渲染(您已经拥有)。

将“遍历树”代码重写为IEnumerable<node>,以便逐个选择节点。对于任何顺序,都存在树遍历的非递归版本,因此您可以使用“yield return”来生成迭代器。您应该能够创建简单的测试来验证代码(没有必要的UI)。

在计时器回调中,只需从迭代器中取出下一个项目直到完成。

答案 3 :(得分:0)

一种可能的解决方案是生成一个单独的线程,然后使用该线程以定期间隔调用DrawNode函数。这样就不会阻止UI线程。

由于您生成的线程不是UI线程,因此您需要在UI线程上显式调用DrawNode函数。这是一种可行的方法:

How to update the GUI from another thread in C#?