1

我一直在开发一个程序,该程序可以直观地输出二叉树的内容(依次由我自己编写的类表示)。我想在这个程序中包含的最后一个功能是树的后序、中序和前序构造的动画。

事实证明,这比我想象的更具挑战性。这是原始的绘制方法:

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

4 回答 4

4

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

于 2012-04-06T18:05:56.063 回答
2

首先,给自己写一个DrawNodesForLevel(int level)函数。然后从顶层开始,启动计时器,每次计时,DrawNodesForLevel()以适当的级别调用,并增加级别。当你到达终点时,停止计时器。

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

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

状态结构还必须跟踪它最后绘制的是左节点还是右节点,因此它可以接下来绘制适当的节点。

于 2012-04-06T18:14:19.763 回答
1

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

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

比在计时器回调中只需从迭代器中获取下一项,直到全部完成。

于 2012-04-06T19:37:03.460 回答
0

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

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

如何从 C# 中的另一个线程更新 GUI?

于 2012-04-06T18:00:06.187 回答