Creating delay between GUI updates

Viewed 34

I am attempting to create a sorting visualizer in Java using Swing. So far, my program asks the user for the sorting algorithm of their choice (I have excluded the code for selection menu here) and then visually sorts an unsorted array. The only sorting algorithm that I have implemented so far for GUI testing purposes is insertion sort.

Below is the insertion sort algorithm:

public class InsertionSort extends Sorter { // Sorter is an abstract class
    @Override
    public void sort(ArrayVisualization array) { // ArrayVisualization object to change array values
        for (int i = 0; i < array.getArraySize(); i++) {
            int j = i - 1;
            int key = array.getArrayValue(i);
            while (j >= 0 && array.getArrayValue(j) > key) {
                array.delayedSwap(j, j+1); // calls delayedSwap method continually-PROBLEM
                --j;
            }
        }
    }

    @Override
    public String toString() {
        return "Insertion Sort";
    }
}

I am unable to correctly visualize the swaps between the various indices. At first, I attempted using Thread.sleep() to briefly put the thread to sleep when swapping values. This inevitably ended up freezing the EDT. After searching through Stack Overflow, I came across Swing Timer which is recommended when making GUI modifications.

When I used Swing Timer for shuffling the array in shuffleArray(), it worked perfectly fine and I could see each value being swapped with a delay (currently, the portion in shuffleArray() that used Swing Timer is commented because I decided later to not to show the shuffling part with delays). When I implemented the delayedSwap() method in a similar fashion with Swing Timer, the array slightly sorted itself but then suddenly stopped. The array remains unsorted as a whole.

delayedSwap() is called appropriate number of times and the insertion sort algorithm works perfectly fine as well when tested without Swing Timer.

Can someone please tell me why am I having this issue with Swing Timer in delayedSwap()? I have tried using SwingWorker and creating a new thread in delayedSwap() and then putting that to sleep. I kind of think it has something to do with me calling delayedSwap() repeatedly in a loop from another class but I do not understand how else to implement delayedSwap() as that is used in all sorting algorithms (roughly). If this is not fixable, is there any other alternative?

Below is the code for the ArrayVisualizer which contains delayedSwap().

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

public class ArrayVisualization extends JPanel {
    private int[] array;
    private static final int BAR_WIDTH = 5;
    private final static int WIN_WIDTH = 1280;
    private final static int WIN_HEIGHT = 720;
    private static final int NUM_BARS = WIN_WIDTH / BAR_WIDTH;
    //private static final int SWAP_DELAY = 1;


    public void arrayScreen() {
        JFrame frame = new JFrame("Sorting Visualizer");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.setLayout(new BorderLayout());

        ArrayVisualization panel = new ArrayVisualization();
        frame.add(panel);

        frame.setSize(WIN_WIDTH, WIN_HEIGHT);
        frame.setVisible(true);
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
    }

    public int getArraySize() {
        return this.array.length;
    }

    public int getArrayValue(int x) {
        return this.array[x];
    }

    public ArrayVisualization() { 
        array = new int[NUM_BARS];
        for (int i = 0; i < NUM_BARS; i++) { // populate the array
            array[i] = i;
        }
        shuffleArray(); // shuffle
        InsertionSort sort = new InsertionSort();
        sort.sort(this); // sort
    }

    public void shuffleArray() {
        Random rand = new Random();

        for (int i = 0; i < NUM_BARS; i++) {
            int randIdx = rand.nextInt(NUM_BARS - 1); // create random indices to swap with
            int temp = array[randIdx];
            array[randIdx] = array[i];
            array[i] = temp;
        }
        repaint(); 

 /*       Timer timer = new Timer(1, new ActionListener() {
            private int counter;

            @Override
            public void actionPerformed(ActionEvent e) {
                int randomNumber = rand.nextInt(NUM_BARS - 1);

                int temp = array[counter];
                array[counter] = array[randomNumber];
                array[randomNumber] = temp;
                repaint();
                if (counter == NUM_BARS - 1) {
                    ((Timer)e.getSource()).stop();
                }
                counter++;
            }
        });
        timer.start();*/
    }

/* MAIN PROBLEM */
    public void delayedSwap(int idx1, int idx2) {
        Timer timer = new Timer(1000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                int temp = array[idx1];
                array[idx1] = array[idx2];
                array[idx2] = temp;
                repaint();
                ((Timer) e.getSource()).stop(); // so that timer terminates after 1 swap
            }
        });
        timer.start();
    }


    @Override
    public void paintComponent(Graphics g) {
        Graphics2D graphics = (Graphics2D) g;
        super.paintComponent(graphics);


        graphics.setColor(Color.BLACK);
        for (int i = 0; i < NUM_BARS; i++) {
            int height = array[i] * 2;
            int xBegin = i + (BAR_WIDTH - 1) * i;
            int yBegin = WIN_HEIGHT - height;

            graphics.fillRect(xBegin, yBegin, BAR_WIDTH, height);
        }
    }
}

Thank you!

1 Answers

You're using your Timer incorrectly. Understand that a Swing Timer is to be used in place of the while loop, so the timer's actionPerformed method is called repeatedly, just like in a loop, but with a Swing-tolerable delay. Your own Timer instead is recreated and restarted repeatedly and calls itself only once and stops, completely blocking it from doing what it's supposed to be doing.

Solution:

  • Get rid of both the for loop and the while loop
  • Use the Timer to do the for loop and while loop's inner actions
  • Do not stop the Timer immediately but from within an if condition -- when the looping should end.
  • Use if blocks to simulate outer for loop and inner while loop functionality

For example your sort method could look as simple as this:

@Override
public void sort(ArrayVisualization array) { // ArrayVisualization object to change array values
    int timerDelay = 400;  // or however many msecs desired
    new Timer(new TimerListener(array), timerDelay).start();        
}

and...

class TimerListener implements ActionListener {
    private ArrayVisualization array;
    private int arrayIndex = 0;
    private int innerIndex = 0;
    int key = 0;
    private boolean insideInnerLoop = false;

    public TimerListener(ArrayVisualization array) {
        this.array = array;
    }

    public void actionPerformed(ActionEvent e) {

        if (!insideInnerLoop) {
            insideInnerLoop = true;
            innerIndex = arrayIndex - 1;
            key = array.getArrayValue(arrayIndex);
        } else {
            if (innerIndex < 0 || array.getArrayValue(innerIndex) > key) {
                insideInnerLoop = false;
                arrayIndex++;
                break;
            } else {
                // swap code here
                innerIndex--;
            }
        }
        // ......


        if (arrayIndex >= array.getArraySize()) {
            ((Timer) e.getSource()).stop();
        }
    }
}

(code not tested...)


Another option, if you wish to preserve your own loops is to use a SwingWorker as a background thread, and using Thread.sleep or similar delay code within the worker's doInBackground method. This will work as well but will require care so as to not run afoul of Swing threading rules, including you should use the publish/process method pair to send intermediate results back to the GUI for display.

Related