我一直致力于一个可视化输出二叉树内容的程序(依次由我自己编写的类表示)。我希望在此程序中包含的最后一个功能是树的后序,顺序和预订构造的动画。
事实证明这比我想象的更具挑战性。这是最初的绘图方法:
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();
}
};
}
}
答案 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函数。这是一种可行的方法: