1011 1110
1011 1110

Reputation: 781

Swing repaint on call

I'm using Java Swing to show a problem being solved recursively.

Every time there is a problem that is solved I'd like to repaint my GUI (Solved after if it passes queenSafe method).

However I'm having an issue here.

I understand that the Event handler and graphics is controlled by the same thread so I can't just tell the thread to sleep to update the GUI when the flag is set. So creating a new thread is best? but not so sure on how to implement it. As of right now I'm trying to use a flag to tell the other thread to repaint. But this seems to give me an infinite loop. Any suggestions?

public void queensProblem(){
    this.guiUpdate();
    boolean solved = solveQueenBlindSearch(0);
    if(!solved){
        JOptionPane.showMessageDialog(gui, "Sorry, but this is not solvable");
    }        
}
boolean solveQueenBlindSearch(int col)
{
    if (col >= cb.queens)
        return true;

    for (int row = 0; row < cb.columns; row++)
    {
        if (queenSafe(row, col))
        {
            cb.board[row][col].piece = new Queen();
            this.flag = true;
            if (solveQueenBlindSearch(col + 1) == true)
                return true;
            cb.board[row][col].piece = null;
        }
    }
    return false;
}
void guiUpdate() {
    new Thread() {
        public void run() {
            while(true){
                if(flag){
                    mainLayout.removeAll();
                    mainLayout.add(searches);
                    JPanel newChessboard = cb.drawChessboard();
                    mainLayout.add(newChessboard);
                    mainLayout.repaint();
                    flag = false;
                }
            }
        }        
   }.run();
}

Upvotes: 0

Views: 95

Answers (1)

d.j.brown
d.j.brown

Reputation: 1842

As MadProgrammer pointed out you could use a SwingWorker to facilitate this sort of behavior. However, I've provide a small example (not specific to your program) demonstrating how this delegation to background threads and updating the GUI on the event dispatch thread (EDT) can work.

Note, this is just one method you could adopt.

The example includes two classes, GuiWorker which is where all the thread handling occurs, and ExampleFrame which uses GuiWorker to provide an example.

GuiWorker

This is an abstract class which defines the execution process, running the relevent tasks on the correct thread. It has two abstract methods backgroundProcess() and postExecute() which must be implemented.

backgroundProcess() will not be run on the EDT, but a background thread. This is run before postExecute()

postExecute() will be run on the EDT and is where the GUI updates after the background task has finished should be carried out.

import javax.swing.SwingUtilities;

public abstract class GuiWorker {

    public abstract void backgroundProcess(); // method called on background thread

    public abstract void postExecute(); // method called on EDT

    public void execute() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Running backgroundProcess() on EDT: " + SwingUtilities.isEventDispatchThread());

                // Execute backgroundProcess() on this background thread
                backgroundProcess();

                // When backgroundProcess() pops, run postExecute() on the EDT
                System.out.println("End of background process.");
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("Running postExecute() on EDT: " + SwingUtilities.isEventDispatchThread());
                        postExecute();
                    }
                });
            }
        }).start(); // start background thread
    }

}

ExampleFrame

This is just a small (and unimpressive!) GUI with a label. The main(String[] args) method is also defined here to reduce the number of classes in this example.

The main(String args) method (entry point) will construct a new ExampleFrame instance on the EDT using SwingUtilities.invokeLater(Runnable)

For simplicity everything is carried out in the constructor. Setting up and showing the GUI with a single JLabel called output which initially has the text 'Initial', as well as using GuiWorker to do some background task. In this case, it will perform 10 iterations of a while loop, outputting i to the console (which increases by 1 with each iteration). Each iteration has a short pause on the background thread of 500ms. Once this is finished, the JLabel called output will be updated to say 'Finished'.

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class ExampleFrame extends JFrame {

    private JLabel output = new JLabel("Initial");

    public static void main(String[] args) {
        // Construct and show a new JFrame (ExampleFrame) on the EDT
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ExampleFrame();
            }
        });
    }

    public ExampleFrame() {
        System.out.println("Running ExampleFrame() constructor on EDT: " + SwingUtilities.isEventDispatchThread());

        // Setup GUI
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        add(output);
        pack();
        setVisible(true);

        // Implement the abstract methods of GuiWorker and invoke execute() to run
        new GuiWorker() {

            @Override
            public void backgroundProcess() {
                // To be run on a background thread

                int i = 0;
                // iterate 10 times, sleeping for 500 ms
                // printing i to the console
                while (i < 10) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    System.out.println(i);
                    i++;
                }
            }

            @Override
            public void postExecute() {
                // when the backgroundProcess has finished
                // update the output JLabel on the EDT
                output.setText("Finished");
            }
        }.execute(); // invoke execute to start the worker
    }

}

If data during execution of the background task is required for the GUI update you can always introduce class member fields when implementing GuiWorker as an anonymous class or otherwise which would then be accessible by postExecute(). Alternatively GuiWorker can be reworked to allow backgroundProcess() to return some data which is then passed to postExecute() as a parameter.

Upvotes: 1

Related Questions