0

How do I get a method to update the GUI before it has finished execution?

I am trying to create a maze game. The solving is done using a flood solving algorithm. I have coded the solving generating and displaying for the maze.

When calling these functions from the JButton ActionListener the maze view frame is created but doesn't get updated until the function is run. It all works when not calling the method from the button.

Currently, I have 5 classes for the maze's functionality:

  • Maze: The methods to start off the generating and solving of the maze
  • Maze_view: The methods to create and update the maze view panels
  • Maze_solve: The methods to 'solve' the maze
  • Maze_generate: The methods to generate the maze
  • Maze_options: A small class that creates the buttons and tries to run the methods in Maze

I also have separate classes for each type of maze 'piece' I need, which allows me to display them in maze view. One parent class Maze_piece contains the location of the 'piece'. The rest inherit from it with Maze_wall, Maze_start, Maze_end, Maze_staticSolver and Maze_player and Maze_solver.

Maze:

import java.io.IOException;
import java.awt.Color;
import java.awt.event.KeyEvent;

public class Maze
{   
    // Global variables for use in generating and solving the maze
    public static Maze_piece[][] maze;
    public static int[] start;
    public static int[] end;
    public static int pause_time;
    public static int size;

// For keyboard input
public static String direction;

// A graphical view of the simulation.
public static Maze_view MazeView; 

/**
 * Runs maze method based upon user input
 */
public static void main(String[] args) {
    // When using Maze_options the Maze_view does not get updates until the method has finished
    Maze_options optionsObject = new Maze_options();
    optionsObject.mazeOptions();
}

/**
 * Generates a maze from the text file a visually displays the attempt at solving it
 */
public static void mazeGenTextFile()
{
    // Creates the view of the maze with different colours for different classes (Maze_pieces)
    MazeView = createMazeView();
    
    // Generating the maze from the text file using a dummy Maze_generateFromTxtFile object
    // in order to use the generateMazeFromTxtFile method
    Maze_generate generateMazeObject = new Maze_generate();
    try {
        maze = generateMazeObject.generateMazeFromTxtFile("maze_blueprint.txt");
    } catch (IOException e){
    }
    
    // Start solving the maze using a dummy solveMazeObject object
    // in order to use the solveMaze method
    Maze_solve solveMazeObject = new Maze_solve();
    solveMazeObject.solveMaze(true);
}    

/**
 * Generates a random maze until one is solveable
 * Display determines if the flood solving algorithm is displayed
 */
public static Maze_piece[][] mazeGenRandom(boolean display, int Size)
{
    // Creates two dummy object in order to use methods from theyre classes
    Maze_generate generateMazeObject = new Maze_generate();
    Maze_solve solveMazeObject = new Maze_solve();
    
    // Creates the view of the maze with different colours for different classes (Maze_pieces)
    MazeView = createMazeView();
    
    // Repetively generate and attempt to solve the random maze until one is solveable
    boolean solved = false;
    while (!solved){
        // Generate a random maze
        maze = generateMazeObject.generateRandomMaze(Size,0);
        // Start solving the maze
        solved = solveMazeObject.solveMaze(display);
    }
    
    // Returns solveable random maze
    return maze;
}

/**
 * Makes use of the above method and then lets the user to solve it
 */
public static void mazeGenRandomToSolve(int Size)
{
    // Retuns a solveable maze which has not visually been solved
    maze = mazeGenRandom(false,Size);
    Maze_generate generateMazeObject = new Maze_generate();
    Maze_solve solveMazeObject = new Maze_solve();
    
    // Clear maze of solvers
    for (int row = 1; row<(size-1); row++){
        for (int col = 1; col<(size-1); col++){
            if (maze[row][col] instanceof Maze_solver || maze[row][col] instanceof Maze_staticSolver){
                generateMazeObject.generatePiece("0",maze,row,col);
            }
        }
    }
    
    // Add player to the maze
    generateMazeObject.generatePiece("X",maze,start[0],1);
    MazeView.updateMazeView(maze,"Player solving...",1,true);
    
    // Intialises useful variables
    boolean Solved = false;
    direction = " ";
    int steps = 1;
    int currentPos[] = {start[0],1};
    int newPos[] = {start[0],1};
    String solvingState = "Player solving...";
    
    // Loops until the maze has been solved
    while (!Solved){
        // Sets the current position of player to be empty (Corridor)
        generateMazeObject.generatePiece("0",maze,currentPos[0],currentPos[1]);
        
        // Set new possible positions to start of as current position
        // So if the player doesn't need to be moved the player can still be positioned at newPos
        newPos[0] = currentPos[0];
        newPos[1] = currentPos[1];
        
        // Changes the new position for the respective directions
        if (direction.equals("N")){
            newPos[0] = currentPos[0] - 1;
        }
        else if (direction.equals("S")){
            newPos[0] = currentPos[0] + 1;                
        }
        else if (direction.equals("W")){
            newPos[1] = currentPos[1] - 1;
        }
        else if (direction.equals("E")){
            newPos[1] = currentPos[1] + 1;
        }            
        
        // Only if the intended move isn't into a wall or the start is the current position updated
        if (!(maze[newPos[0]][newPos[1]] instanceof Maze_wall) && !(maze[newPos[0]][newPos[1]] instanceof Maze_start)){
            // Only increments steps if new position is differnet to current position
            if ((currentPos[0] != newPos[0]) || (currentPos[1] != newPos[1])){                                    
                steps++;
            }
            // Updates current position
            currentPos[0] = newPos[0];
            currentPos[1] = newPos[1];
        }
        // If the player has reached the end update maze view one last time before exiting the loop
        if (maze[currentPos[0]][currentPos[1]] instanceof Maze_end){
            Solved = true;
            solvingState = "Maze solved!";
        }
        
        // Generate a player in the new position (Might not have been updated)
        generateMazeObject.generatePiece("X",maze,currentPos[0],currentPos[1]);
        // Update maze view
        MazeView.updateMazeView(maze,solvingState,steps,true);
        // Adds a delay so one key press by the user doesn't last mutliple loops
        // and cause mutliple movements
        solveMazeObject.delay(200);
    }
}

/**
 * Determines which key has been pressed and if the direction
 * of the player piece needs to be updated
 */
public void keyPressed(KeyEvent e)
{
    // Gets the key code for which key caused the keyPressed event
    int key = e.getKeyCode();
    
    // Sets the direction based upon which key was pressed
    if (key == KeyEvent.VK_LEFT) {
        direction = "W";
    }
    else if (key == KeyEvent.VK_RIGHT) {
        direction = "E";
    }
    else if (key == KeyEvent.VK_UP) {
        direction = "N";
    }
    else if (key == KeyEvent.VK_DOWN) {
        direction = "S";
    }
}

/**
 * Determines which key has been released and if the direction
 * of the player piece needs to be updated
 */
public void keyReleased(KeyEvent e)
{
    // Gets the key code for which key caused the keyReleased event
    int key = e.getKeyCode();
    
    // Resets the direction to be " " if one of the direction keys has been realsed
    if (key == KeyEvent.VK_LEFT) {
        direction = " ";
    }
    else if (key == KeyEvent.VK_RIGHT) {
        direction = " ";
    }
    else if (key == KeyEvent.VK_UP) {
        direction = " ";
    }
    else if (key == KeyEvent.VK_DOWN) {
        direction = " ";
    }
}
/**
 * Creates and returns a maze view object
 */
public static Maze_view createMazeView()
{
    // Creates the view of the maze with different colours for different classes (Maze_pieces)
    MazeView = new Maze_view(100, 100);
    MazeView.setColour(Maze_wall.class, Color.BLACK);
    MazeView.setColour(Maze_corridor.class, Color.WHITE);
    MazeView.setColour(Maze_start.class, Color.GREEN);
    MazeView.setColour(Maze_end.class, Color.RED);
    MazeView.setColour(Maze_solver.class, Color.BLUE);
    MazeView.setColour(Maze_staticSolver.class, Color.CYAN);
    MazeView.setColour(Maze_player.class, Color.ORANGE);
    
    // Returns the maze view object
    return MazeView;
}
}

Maze_view:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Creates a visulisation of the maze using java.awt classes
 */
public class Maze_view extends JFrame
{
    // Color used for objects that have no defined color (Just a precaution)
    private static final Color UNKNOWN_COLOR = Color.gray;
    
    // A map for storing colours for the Maze_piece
    private Map<Class, Color> colours;
    
    // A global mazeView object
    private MazeView mazeViewPanel;
    
    // A global JPanel for the current solving state output
    private JLabel solvingStateLabel;
    
    
    // A global JPanel for the current step output
    private JLabel stepCountLabel;
    
    /**
     * Constructor to create/intialise the maze view panel
     */
    public Maze_view(int height, int width)
    {
        // Intialise colours hashmap
        colours = new LinkedHashMap<>();
        
        // Intialise maze view
        setTitle("Maze flood solving algorithm");
        setLocation(100, 50);
        Container contents = getContentPane();
        
        // Create and add mazeViewPanel to the content pane in CENTER location
        mazeViewPanel = new MazeView(height, width);
        contents.add(mazeViewPanel, BorderLayout.CENTER);
        
        // Create and add infoPanel to the content pane in SOUTH location
        contents.add(createInfoPanel(), BorderLayout.SOUTH);
        
        // Adds the keyboard listener to the display (So it can listen to keyboard events)
        addKeyListener(new keyboardinput());
        
        // Packs the container and its contents so it's ready to be displayed to the user
        pack();
        // Displays to the user
        setVisible(true);
    }
    
    /**
     * Creates and returns the info panel to the constructor
     */
    public Container createInfoPanel()
    {
        // Intialise label for solving state
        solvingStateLabel = new JLabel("Solving...");
        // Set text to be white and background to be black and not transparent
        solvingStateLabel.setOpaque(true);
        solvingStateLabel.setBackground(Color.BLACK);
        solvingStateLabel.setForeground(Color.WHITE);
        
        // Add a dummy label to seperate the lables
        JLabel seperateLabel = new JLabel(" ");
        seperateLabel.setOpaque(true);
        seperateLabel.setBackground(Color.BLACK);
        
        // Intialise label for step count
        stepCountLabel = new JLabel("Current step: " + 0);
        // Set text to be white and background to be black and not transparent
        stepCountLabel.setOpaque(true);
        stepCountLabel.setBackground(Color.BLACK);
        stepCountLabel.setForeground(Color.WHITE);
        
        // Intilaise and place the two above lables into their container
        // An internal panel is required to allow the two JLabels to be centred
        Container infoPanel = new JPanel();
        Container innerPanel = new JPanel();
        // Add the labels to the innerPanel
        innerPanel.setLayout(new GridLayout(1,3));
        innerPanel.add(solvingStateLabel);
        innerPanel.add(seperateLabel);
        innerPanel.add(stepCountLabel);
        // Add the innerPanel to the infoPanel
        infoPanel.add(innerPanel);
        // Set background to be black
        infoPanel.setBackground(Color.BLACK);
        
        return infoPanel;
    }
    
    /**
     * Updates maze view with new maze, solving state and current step count
     */
    public void updateMazeView(Maze_piece[][] maze, String solvingState, int currentStep, boolean updateMaze)
    {
        // Makes sure the maze view is visibel
        if (!isVisible()){
            setVisible(true);
        }
        
        // Makes sure the maze vew panel is the correct size for the given maze
        int size = maze.length;
        mazeViewPanel.preparePaint(size);
        
        // If the maze is to be updated
        if (updateMaze){
            // 'Paints' the maze onto the maze panel, row by col
            // Using the maze pieces respective colour 
            for (int row = 0; row<size; row++){
                for (int col = 0; col<size; col++){
                    mazeViewPanel.drawPiece(row, col, getColour(maze[row][col].getClass()));
                }
            }
        }
        
        // Updates solving sate so it alternates between 'Solving...' and 'Solving..'
        // solvingState is set to be 'Solving...' by Maze_solve while solving so on even steps its replaced with 'Solving..'
        solvingStateLabel.setForeground(Color.WHITE);
        if (solvingState.equals("Solving...") && (currentStep%2==0)){
            solvingState = "Solving..";
        }
        // If the maze is not solveable change text colour to red
        else if (solvingState.equals("Maze not solveable")) {
            solvingStateLabel.setForeground(Color.RED);
        }
        // If the maze has been solved change text colour to greem
        else if (solvingState.equals("Maze solved!")){
            solvingStateLabel.setForeground(Color.GREEN);
        }
        
        // Update solvingStateLabel and currentStepLabel
        solvingStateLabel.setText(solvingState);
        stepCountLabel.setText("Current step: " + currentStep);
        
        // Paint over old representation of maze with new updated one
        mazeViewPanel.repaint();
    }
    
    /**
     * Define a color to be used for a given class
     */
    public void setColour(Class mazePieceClass, Color colour)
    {
        colours.put(mazePieceClass, colour);
    }

    /**
     * Returns the colour for the given Maze_piece
     */
    private Color getColour(Class mazePieceClass)
    {
        Color colour = colours.get(mazePieceClass);
        if (colour == null){
            // If no color defined for this class
            return UNKNOWN_COLOR;
        }
        else {
            return colour;
        }
    }
    
    /**
     * Provide a graphical view of a sqaure maze. This is 
     * a nested class which defines a custom component for the user interface. This
     * component displays the maze
     */
    private class MazeView extends JPanel
    {
        // Intial global variables for use in MazeView
        private final int GRID_VIEW_SCALING_FACTOR = 6;
        private int gridWidth, gridHeight;
        private int xScale, yScale;
        Dimension size;
        private Graphics graphic;
        private Image mazeImage;

        /**
         * Create a new MazeView component
         */
        public MazeView(int height, int width)
        {
            gridHeight = height;
            gridWidth = width;
            size = new Dimension(0, 0);
        }

        /**
         * Tell the GUI manager how big we would like to be
         */
        public Dimension getPreferredSize()
        {
            return new Dimension(gridWidth * GRID_VIEW_SCALING_FACTOR,
                                 gridHeight * GRID_VIEW_SCALING_FACTOR);
        }

        /**
         * Prepare for a new round of painting. Since the component
         * may be resized, compute the scaling factor again
         */
        public void preparePaint(int Size)
        {
            // If the size has changed
            if (! size.equals(getSize())){
                // Create new image and graphic to display the maze upon
                size = getSize();
                mazeImage = mazeViewPanel.createImage(size.width, size.height);
                graphic = mazeImage.getGraphics();
                
                // Sets the scaling for x and y based of current size of the image
                // If less than 1 set to be the preset of grid view scaling factor
                xScale = (size.width / gridWidth) * Math.round(100/Size);
                if (xScale < 1){
                    xScale = GRID_VIEW_SCALING_FACTOR;
                }
                yScale = (size.height / gridHeight) * Math.round(100/Size);
                if (yScale < 1){
                    yScale = GRID_VIEW_SCALING_FACTOR;
                }
            }
        }
        
        /**
         * Paint on grid location on the panel in a given color
         */
        public void drawPiece(int y, int x, Color color)
        {
            // Sets the colour of the graphic
            // Then create a rectangle of that colout in the given position with set size
            graphic.setColor(color);
            graphic.fillRect(x * xScale, y * yScale, xScale-1, yScale-1);
        }

        /**
         * The maze view component needs to be redisplayed. Copy the
         * internal image to screen
         * In essence update the screen
         */
        public void paintComponent(Graphics graphic)
        {
            // If there is an image to 'paint' from 
            if (mazeImage != null){
                Dimension currentSize = getSize();
                // If the image doesn't need to be resized
                if (size.equals(currentSize)){ 
                    // Paint the image onto the graphic to display
                    graphic.drawImage(mazeImage, 0, 0, null);
                }
                else {
                    // Rescale the previous image
                    // Then paint the image onto the graphic to display
                    graphic.drawImage(mazeImage, 0, 0, currentSize.width, currentSize.height, null);
                }
            }
        }
    }    
    
    /**
     * Listens for keyboard events
     */
    private class keyboardinput extends KeyAdapter {
        // When a key is pressed runs keyPressed in Maze and passes the event
        @Override
        public void keyPressed(KeyEvent e) {
            // Uses dummy object to call Maze method
            Maze mazeObject = new Maze();
            mazeObject.keyPressed(e);
        }
        
        // When a key is released runs keyPressed in Maze and passes the event
        @Override
        public void keyReleased(KeyEvent e) {
            // Uses dummy object to call Maze method
            Maze mazeObject = new Maze();
            mazeObject.keyReleased(e);
        }
    }
}   

Maze_solve:

 import java.util.*;
import java.io.IOException;

/**
 * Solves the maze defined its parent class (maze) using the flood solving algorithm
 */
public class Maze_solve extends Maze
{
    /**
     * A dummy constructor to allow the call of its method from its parent class (Maze)
     */
    public Maze_solve(){
    }
    
    /**
     * Solves the maze defined its parent class (maze) using the flood solving algorithm
     */
    public boolean solveMaze(boolean display){
        // Initialising first maze solver(s) at start
        // Pass in intial maze solver with position of start, steps left being the max possible for a maze of size size
        newSolvers(maze,size,new Maze_solver(start, size*size));
        
        // Solving/updating loop
        boolean solved = false;
        boolean stillSolving = true; // If there are any solvers left
        
        // Array to contain all the currently alive solvers (Can hold whole maze of solvers)
        Maze_solver[] solvers = new Maze_solver[size*size];
        int nextIndex;
        
        // Displays first maze visulisation
        MazeView.updateMazeView(maze,"Solving...",1,true);
        
        // Add slight delay so the maze view has time to load and be seen before starting to solve the maze
        delay(500);
        
        // Intilaise steps done for use in the solving loop 
        int stepsDone = 0;
        
        // Continue to update all solvers until thhe solvers have found the exit or all been removed
        while (!solved && stillSolving){
            // Choosen delay
            delay(pause_time);
            
            // Reset variables
            stillSolving = false;
            Arrays.fill(solvers,null); // Clearing array from last update
            nextIndex = 0;
            
            // Collecting all current alive solvers to the array
            for (int row=0; row<size; row++){
                for (int col=0; col<size; col++){
                    // If we find a solver we add it to the solvers array
                    if (maze[row][col] instanceof Maze_solver){
                        // Checks if the current sovler has made it to the end
                        if ((row == end[0]) && (col == end[1])){
                            solved = true;
                            // Reset end position to be of class Maze_end
                            Maze_generate generatePieceObject = new Maze_generate();
                            generatePieceObject.generatePiece("=",maze,end[0],end[1]);
                        }
                        else {
                            solvers[nextIndex] = (Maze_solver) maze[row][col];
                        }
                        nextIndex++;
                        stillSolving = true;
                    }
                }
            }
            
            // Dont update solvers if the maze has been solved or not solveable
            if(!solved && stillSolving){
                // Updating all current alive solvers
                for (int solver_index=0; solver_index<nextIndex; solver_index++){
                    // Gets postion for current solver
                    Maze_solver tempSolver = solvers[solver_index];
                    
                    // Creates new solvers based upon its current position
                    newSolvers(maze, size, tempSolver);
                    
                    // Replaces current location with static solver (In effect removing itself) to prevent backtracking
                    int[] pos = tempSolver.getLocation();
                    maze[pos[0]][pos[1]] = new Maze_staticSolver(pos);
                }
            }
            // Updates the maze display
            String solvingState = "Solving...";
            if (solved){
                solvingState = "Maze solved!";
            }
            else if (!stillSolving){
                solvingState = "Maze not solveable";
            }
            else if (stillSolving){
                stepsDone = (int) Math.abs(solvers[0].getStepsLeft() - Math.pow(size,2));
            }

            MazeView.updateMazeView(maze, solvingState,stepsDone, display);
        }
        // Returns if the maze is solveable or not
        return solved;
    }
    
    /**
     * Intilaises new maze solvers based on current positon and possible positions for the new solvers
     * And excludes going back on itself
     */
    public static void newSolvers(Maze_piece[][] maze, int size, Maze_solver currentSolver){
        int[] currentPos = currentSolver.getLocation();
        int currentStepsLeft = currentSolver.getStepsLeft();
        // Checks to make sure the current solver hasn't run out of steps
        if (currentStepsLeft > 0) {
            // Checks if new possible position is out of the maze, empty (Maze_corridor) or the end of the maze
            // I had to remove the code as I had to many characters
            }
        }
    }
    
    /**
     * Pause for a given time in milliseconds
     */
    public static void delay(int millisec)
    {
        try {
            Thread.sleep(millisec);
        }
        catch (InterruptedException ie) {
            // wake up
        }
    }
}

Maze_generate:

I couldn't include Maze_generate as there were too many characters

Maze_options:

import java.awt.Color;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * Creates the option buttons for the maze generation and solving types
 * Start off the maze methods
 */
public class Maze_options extends JFrame
{  
    /**
     * Creates the maze option buttons
     */
    public void mazeOptions()
    {
        // Get content pane
        JFrame frame = new JFrame("Maze options");
        //setTitle("Maze options");
        //setLocation(100, 50);
        Container contents = frame.getContentPane();
        
        // Intialise option panel
        Container optionPanel = new JPanel();
        optionPanel.setLayout(new GridLayout(1,3));
        
    // Create a dummy maze object to use maze methods
        Maze mazeObject = new Maze();
        
        // Create and add buttons for the panel
        // Button for generating and solving a maze from a text file
        JButton textFileButton = new JButton("Solve text file maze");
            textFileButton.addActionListener(ev -> runMazeGenTextFile());
            optionPanel.add(textFileButton);
        // Button for generating and attempting to solve a random maze until it is solvealbe
        JButton randomMazeButton = new JButton("Solve random maze");
            randomMazeButton.addActionListener(ev -> mazeObject.mazeGenRandom(true,10));
            optionPanel.add(randomMazeButton);
        // Button for doing the above and then let the user solve the maze
        JButton userMazeButton = new JButton("User solve random maze");
            userMazeButton.addActionListener(ev -> mazeObject.mazeGenRandomToSolve(10));
            optionPanel.add(userMazeButton);
        
        // Adding option panel to contents and then packing and displaying it
        contents.add(optionPanel);
        frame.pack();
        frame.setVisible(true);
    }
    
    public void runMazeGenTextFile(){
        // Create a dummy maze object to use maze methods
        Maze mazeObject = new Maze();
        mazeObject.mazeGenTextFile();
    }
}

Thank you for taking the time to read my question and my code, hopefully, I have provided enough comments and a good enough description.

Andrew Thompson
  • 166,747
  • 40
  • 210
  • 420
J.Garry
  • 15
  • 1
  • 5
  • 8
    Code invoked from a listener executes on the `Event Dispatch Thread (EDT)`. The GUI paints itself on the EDT so it can't get painted until the maze logic finishes executing. The solution is to execute the maze logic on a separate Thread, typically by using a `SwingWorker`. See the Swing tutorial on [Concurrency](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html). If you are trying to animate each step of the maze then check out: https://stackoverflow.com/a/64196256/131872 for a working example of animation. – camickr Oct 25 '21 at 20:48

0 Answers0