1

I've been working on a program that visually outputs the contents of a binary tree (represented in turn by classes that I wrote myself). The last feature I want to include in this program is an animation of the postorder, inorder, and preorder construction of the tree.

This has proven much more challenging than I thought. Here's the original Draw Method:

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;
    }

My original idea was to simply put Thread.Sleep(1000) inside each of the first two if clauses - all I really needed to do was pause the execution of the program for 1 second before each drawing of a node.

I realized that the Sleep method was blocking the execution of the drawing code, so I gave up on that method.. I then tried to use Timers, but found it impossibly difficult when dealing with the tree.

My goal is to simply find a way to pause program execution without disrupting the GUI's responsiveness and without overly complicating the code..

Any help would be appreciated :).

Edit: Some potentially relevant information: The program runs on Winforms, all graphics are handled via GDI+. If you need any other information, just ask :)

Edit: For 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();
                    }
                };
            }
        }
Daniel
  • 2,944
  • 3
  • 22
  • 40
  • 1
    You need to use a timer. What did you have trouble with? – SLaks Apr 06 '12 at 17:53
  • 1
    You could also use `await Task.Delay(...)` in C# 5 – SLaks Apr 06 '12 at 17:54
  • The timer didn't work because I need the animation to be postorder, and I couldn't find a way to make it work with timers (I had two). Here's the code, if you can find a way to make it work :): Edit: Character limitation prevents me from putting code here, I put it in the original post. – Daniel Apr 06 '12 at 17:56
  • are you using .Net 4.0 or 4.5? – Sofian Hnaide Apr 06 '12 at 18:09
  • I have Visual Studio 11 open, I can't seem to find this Task.Delay method.. I included System.Threading.Tasks and wrote the exact line of code you mentioned. Edit: I programmed it in 4.0, but I can easily switch to 4.5.. – Daniel Apr 06 '12 at 18:11
  • Why are you using a Beta product? Disregard anyone who suggest 4.5 at this point in time. Its not supported by Microsoft and not fully tested. – Security Hound Apr 06 '12 at 18:16
  • @Ramhound: Because it's the easiest way to do this _by far_. – SLaks Apr 06 '12 at 18:54

4 Answers4

4

Use a timer and set an appropriate interval for updates. In the Tick event perform the next step of drawing and display it.

Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • I tried (I posted the code in an edit), I really can't get it to work with a binary tree, you have to account for both left and right node drawings and have to make sure they are all delayed properly.. – Daniel Apr 06 '12 at 18:14
  • 1
    It works, you just need to maintain your state properly. You don't need two timers, you just need to keep track of what you drew last and what you want to draw each time the timer ticks. – Ed S. Apr 06 '12 at 18:31
2

First, write yourself a DrawNodesForLevel(int level) function. Then start at the top level, start the timer, every time it ticks, call DrawNodesForLevel() with the appropriate level, and increment the level. When you get to the end, stop the timer.

EDIT: Updated with the understanding that you want to pause between each node, not each level.

Move the variables in your function to their own DrawNodeState class, and pass the instance of that class whenever you call DrawNode(). Then, instead of having DrawNode() call itself, have DrawNode() start a timer (also part of the DrawNodeState class). When that timer ticks, the tick function calls DrawNode() and passes it the state structure.

The state structure is also going to have to track whether it last drew the left or right node, so it can draw the appropriate node next.

Katie Kilian
  • 6,815
  • 5
  • 41
  • 64
  • Don't forget to upvote/accept the answers that helped you. (Not just mine!) – Katie Kilian Apr 06 '12 at 18:18
  • I just found a problem, though: for each node, I want to draw the children with a 1 second interval between them; this is, first I'll draw the left child, wait 1 second, then draw the second child.. Your suggestion doesn't really allow that, unless I add a second timer, but then I have to make sure that it's synced with the other timer... – Daniel Apr 06 '12 at 18:23
1

Split your code into 2 parts - one traversing the tree, another rendering (which you already have).

Rewrite your "traverse tree" code to be IEnumerable<node> so you can pick nodes one by one. There are non-recursive verions of tree traversal for any order, so you can use "yield return" to make iterator. You should be able to create simple tests to verify the code (no UI necessary for this).

Than in the timer callback simply take next item from the iterator till all done.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
0

One possible solution would be to spawn a separate thread, then use that thread to invoke the DrawNode function at a periodic interval. This way the UI thread will not be blocked.

Since the thread you spawned will not be the UI thread, you will need to explicitly invoke the DrawNode function on the UI thread. Here's one potential way of doing that:

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

Community
  • 1
  • 1
Conman27
  • 11
  • 2
  • A timer is built for this and you don't have to worry about cross thread issues as the `Tick` event runs on the UI thread (assuming that the time it takes to generate the image is not too long and running that on the UI thread is ok). – Ed S. Apr 06 '12 at 18:06