2

I'm doing an 8 Puzzle solver that ultimately stores each node (int[] of elements 0-8) in the path to put the blocks in order in a stack. I have a WPF GUI that displays an int[,]

foreach (var node in stack)
        {
            int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of  int[,]
            blocks.setBoard(unstrung); // sets the board to pass in to the GUI
            DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
            Thread.Sleep(500);
        }

The GUI displays the initial board, and then after I click solve, the final (in order) board is displayed correctly. What I want to do is display each node on the board for some amount of time, ultimately arriving at the in-order board. With Thread.Sleep, the GUI will simply pause for the set amount of time before displaying the final node. Any ideas as to why it this code wouldn't display the board at each node every 500ms?

For reference, here's an example output from Console.Write for the nodes:
4,2,3,6,1,0,7,5,8
4,2,0,6,1,3,7,5,8
4,0,2,6,1,3,7,5,8
4,1,2,6,0,3,7,5,8
4,1,2,0,6,3,7,5,8
0,1,2,4,6,3,7,5,8
1,0,2,4,6,3,7,5,8
1,2,0,4,6,3,7,5,8
1,2,3,4,6,0,7,5,8
1,2,3,4,0,6,7,5,8
1,2,3,4,5,6,7,0,8
1,2,3,4,5,6,7,8,0

robokode
  • 23
  • 6
  • 2
    That's why we have a timer. Or use `await Task.Delay(500)` – Sriram Sakthivel Apr 29 '14 at 16:50
  • 5
    I'm not sure what you're trying to do, but 1 - Don't use `int[,]`. Create a proper data model instead. And 2 - Don't use `Thread.Sleep()` in the UI thread unless you want your application to completely freeze up all the time. – Federico Berasategui Apr 29 '14 at 16:52
  • 3
    If you're doing the sleeping on the UI Thread, the UI will never be updated. – Michael Todd Apr 29 '14 at 16:52
  • 2
    This loop is running in the main thread. The main thread is the UI thread. It can't paint or redraw anything while your code is executing. You should never, ever sleep the main thread. There are a hundred solutions to this problem - timers, threads, events, tasks, etc. Pick one. The "Related" topics over there might help ---------------->>>>>>>>>>>>>>> – J... Apr 29 '14 at 16:52
  • possible duplicate of [How to add a delay to a WPF program without blocking the UI](http://stackoverflow.com/questions/16176871/how-to-add-a-delay-to-a-wpf-program-without-blocking-the-ui) – J... Apr 29 '14 at 16:55
  • maybe [this](http://social.msdn.microsoft.com/Forums/vstudio/en-US/878ea631-c76e-49e8-9e25-81e76a3c4fe3/refresh-the-gui-in-a-wpf-application?forum=wpf) can help – Pdksock Apr 29 '14 at 17:06
  • 2
    look into [DispatcherTimer](http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer.aspx) and bind to the tick event – mhoward Apr 29 '14 at 17:32

1 Answers1

2

Edit:

Since my original answer was downvoted for using a Thread instead of a Timer, here is an example using a timer.

The code for using a Thread was just shorter and I wanted to give him a solution quickly.

Also, using a Thread instead of a timer meant he didn't need to pass parameters differently or restructure his loop.

This is why it is a good idea to discuss pros/cons of alternate solutions instead of simply insisting that there is only one right way.

Use the timer_Tick function to update the position.

You might notice that this complicates the original code since you will have to pass parameters differently and restructure your loop.

public partial class Form1 : Form
{
    private Point pos = new Point(1,1);
    private float[] vel = new float[2];
    private Size bounds = new Size(20,20);
    private Timer ticky = new Timer();      //System.Windows.Forms.Timer
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        ticky.Interval = 20;
        ticky.Tick += ticky_Tick;
        vel[0] = 4; vel[1] = 0;
        ticky.Start();
    }
    void ticky_Tick(object sender, EventArgs e)
    {
        updatePosition();
        //This tells our form to repaint itself (and call the OnPaint method)
        this.Invalidate();
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        e.Graphics.FillEllipse(new SolidBrush(Color.LightBlue), new Rectangle(pos, bounds));
    }
    private void updatePosition()
    {
        pos = new Point(pos.X + (int)vel[0], pos.Y + (int)vel[1]);
        vel[1] += .5f; //Apply some gravity
        if (pos.X + bounds.Width > this.ClientSize.Width)
        {
            vel[0] *= -1;
            pos.X = this.ClientSize.Width - bounds.Width;
        }
        else if (pos.X < 0)
        {
            vel[0] *= -1;
            pos.X = 0;
        }
        if (pos.Y + bounds.Height > this.ClientSize.Height)
        {
            vel[1] *= -.90f; //Lose some velocity when bouncing off the ground
            pos.Y = this.ClientSize.Height - bounds.Height;
        }
        else if (pos.Y < 0)
        {
            vel[1] *= -1;
            pos.Y = 0;
        }
    }
}

Results:

Results

You can use timers to do all sorts of delayed form drawing:

More

Original Solution:

//Create a separate thread so that the GUI thread doesn't sleep through updates:
using System.Threading;

new Thread(() => {
    foreach (var node in stack)
    {
        //The invoke only needs to be used when updating GUI Elements
        this.Invoke((MethodInvoker)delegate() {
            //Everything inside of this Invoke runs on the GUI Thread
            int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of  int[,]
            blocks.setBoard(unstrung); // sets the board to pass in to the GUI
            DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
        });
        Thread.Sleep(500);
    }
}).Start();

Solution in 2022:

await Task.Delay(500);

Things really are better these days.

user1274820
  • 7,786
  • 3
  • 37
  • 74
  • I changed the delegates to lambda expressions since they're shorter. Not sure why I caught a downvote? This fixes the issue. – user1274820 Apr 29 '14 at 17:30
  • 2
    You should not be creating a separate thread just to have it spend essentially *all* of its time sleeping. You should be using a Timer instead. – Servy Apr 29 '14 at 17:31
  • 2
    No, a timer *doesn't* do the exact same thing. A timer will not consume a thread at *all* between the intervals. You're going through all of the effort to create, and eventually tear down, a thread, which is expensive, you're adding in context switches, which are again, expensive, you're consuming a lot of memory, and so on. Adding new threads comes with a fairly steep cost. – Servy Apr 29 '14 at 17:37
  • 1
    Thank you very much for the solution user1274820! I'm going to try implementing this as well as looking around to figure out how to use a Timer. I have no experience with this type of functionality whatsoever so I will figure out a solution using a Timer on my own. Interesting discussion too. Thank you again for taking the time for this! – robokode Apr 29 '14 at 17:56
  • -1 There are two different classes (that I know of) called `Timer` in the .net framework. One of which will work, the other will not. I suggest you make it obvious which one is which, perhaps by using a Full Namespaced Name for Timer. – Aron Jan 08 '16 at 05:22