JPanel 在使用延迟时不会实时更新



我正在创建一个GUI程序来解决一个迷宫。我已经制定了迷宫求解算法,但当我试图添加延迟时,会发生奇怪的事情。

代码相当长,所以在GUI延迟方面也有同样的作用。

import java.awt.*;
import javax.swing.*;
public class Example extends JPanel {
JButton[] buttons = new JButton[4];
public Example() {
setLayout(new GridLayout(1, 4));
for(int i = 0; i < 4; i++)
add(buttons[i] = new JButton());
}
public static class SubClass extends JPanel {
Example example;
JButton start;
public SubClass() {
setLayout(new BorderLayout());
add(example = new Example(), BorderLayout.CENTER);
start = new JButton("Start");
start.addActionListener(e -> {
for(int i = 0; i < 4; i++) {
//insert delays here
example.buttons[i].setBackground(Color.BLACK);
}
});
add(start, BorderLayout.SOUTH);
}
}
public static void main(String[] args){
JFrame frame = new JFrame("Title");
frame.setSize(500, 500);
frame.setLocation(500, 250);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new SubClass());
frame.setVisible(true);
}
}

当我在没有任何延迟的情况下运行代码时,它可以完美地工作。然而,每当我尝试添加延迟,而不是按钮一次更新一个时,整个窗口都会冻结,直到所有按钮都完成更新,然后整个窗口会同时更新。

我尝试使用java.util.Timerjavax.swing.TimerThread.sleep,但它们似乎都不起作用。

此外,当我没有封闭面板(SubClass),只是直接从主方法更改内部面板(Example)时,它确实有效,按钮也会像预期的那样实时更新。

例如,如果我将主要方法更改为

public static void main(String[] args){
Example ex = new Example();
JFrame frame = new JFrame("Title");
frame.setSize(500, 500);
frame.setLocation(500, 250);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(ex);
frame.setVisible(true);
for(int i = 0; i < 4; i++) {
//insert delays here
ex.buttons[i].setBackground(Color.BLACK);
}
}

回想起来,我本应该按照相反的顺序制作面板,切换子类和封闭类,但现在我已经完成了程序的编写,除了这个程序,它在所有方面都能工作,所以我不想做太多额外的工作,也不想重写一堆东西。我也不知道这是否能解决问题。

编辑:这是完整的代码:

import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.util.*;
import javax.swing.border.LineBorder;
public class StackGUIMaze extends JPanel {
public static final String SET_START = "setStart";
public static final String SET_END = "setEnd";
public static final String BUILD = "build";
public static final String RUNNING = "running";
private static final char OPEN = 'O'; //Open
private static final char CLOSED = 'X'; //Closed
private static final char TRIED = '*';
private static final char SUCCEEDED = '+';
private static final char FAILED = '#';
private static final char POINTER = '^';
private static final char END = 'E';
private static final char START = 'S';
private static final int DELAY = 0;
private static final Direction NORTH = Direction.NORTH;
private static final Direction SOUTH = Direction.SOUTH;
private static final Direction EAST = Direction.EAST;
private static final Direction WEST = Direction.WEST;
private static final Direction[] DIRECTIONS = {SOUTH, EAST, NORTH, WEST};
private final int ROWS;
private final int COLUMNS;
private Space pointer;
private Space end;
private Branch moves = new Branch(null, null);
private Space[][] buttons;
private String mode = "";
private Space start;
private Timer timer = new Timer(DELAY, e -> {
step();
if(pointer == end) {
this.timer.stop();
}
});
public StackGUIMaze(int r, int c) {
ROWS = r;
COLUMNS = c;
setLayout(new GridLayout(ROWS, COLUMNS));
buttons = new Space[ROWS][COLUMNS];
for(int i = 0; i < ROWS; i++) {
buttons[i] = new Space[COLUMNS];
for(int j = 0; j < COLUMNS; j++) {
buttons[i][j] = new Space(OPEN, i, j);
buttons[i][j].setBackground(Color.WHITE);
buttons[i][j].setContentAreaFilled(false);
buttons[i][j].setBorder(new LineBorder(new Color(100, 100, 100)));            
buttons[i][j].setOpaque(true);
buttons[i][j].addActionListener(e -> {
if(e.getSource() instanceof Space) {
switch(mode) {
case BUILD:
((Space) e.getSource()).setStatus(((Space) e.getSource()).getStatus() == OPEN ? CLOSED : OPEN); //Toggles state of space
break;
case SET_START:
((Space) e.getSource()).setStatus(START);
break;
case SET_END:
((Space) e.getSource()).setStatus(END);
break;
}
}
});
add(buttons[i][j]);
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Title");
frame.setSize(500, 500);
frame.setLocation(500, 250);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new FullPanel(12, 12));
frame.setVisible(true);
});
}
private static void delay(int n) {
try {Thread.sleep(n);} catch(InterruptedException ignored) {}
}
private void setEnd(int r, int c) {
end = null;
buttons[r][c].setStatus(END);
}
public Space getPointer() {
return pointer;
}
public Space getEnd() {
return end;
}
private void backtrack(){
while(openMoves().isEmpty()) {
pointer.setStatus(FAILED);
move(lastMove().getDirection().opposite());
lastMove().getParent().pop();
}
}
private Space to(Direction d) {
try {
switch (d) { //No breaks necessary, since each case ends in a return statement
case NORTH:
return buttons[pointer.getRow() - 1][pointer.getCol()];
case SOUTH:
return buttons[pointer.getRow() + 1][pointer.getCol()];
case EAST:
return buttons[pointer.getRow()][pointer.getCol() + 1];
case WEST:
return buttons[pointer.getRow()][pointer.getCol() - 1];
default:
System.out.println("I don't know how you got here, somehow a nonexistent direction was called");
return null;
}
} catch(ArrayIndexOutOfBoundsException e) {
return null;
}
}
public void setPointer(int r, int c) {
pointer = buttons[r][c];
}
private Branch lastMove() {
Branch temp = moves;
while(!temp.isEmpty()) {
temp = temp.peek();
}
return temp;
}
private boolean go(Direction d) {
boolean b = checkOpen(d);
pointer.setStatus(TRIED);
if (checkOpen(d)) {
move(d);
if(pointer.getStatus() == OPEN) pointer.setStatus(TRIED);
lastMove().push(new Branch(lastMove(), d));
return true;
} else {
return false;
}
}
private void move(Direction d) {
delay(50);
switch(d) { //No breaks necessary, since each case ends in a return statement
case NORTH:
buttons[pointer.getRow() - 1][pointer.getCol()].setStatus(POINTER);
break;
case SOUTH:
buttons[pointer.getRow() + 1][pointer.getCol()].setStatus(POINTER);
break;
case EAST:
buttons[pointer.getRow()][pointer.getCol() + 1].setStatus(POINTER);
break;
case WEST:
buttons[pointer.getRow()][pointer.getCol() - 1].setStatus(POINTER);
break;
default:
System.out.println("I don't know how you got here, somehow a nonexistent direction was called");
}
}
private ArrayList<Direction> openMoves() {
ArrayList<Direction> arr = new ArrayList<>();
for(Direction d : DIRECTIONS) {
if(checkOpen(d)) arr.add(d);
}
return arr;
}
private boolean checkOpen(Direction d) {
try {
char s;
switch (d) { //No breaks necessary, since each case ends in a return statement
case NORTH:
s = buttons[pointer.getRow() - 1][pointer.getCol()].getStatus();
break;
case SOUTH:
s = buttons[pointer.getRow() + 1][pointer.getCol()].getStatus();
break;
case EAST:
s = buttons[pointer.getRow()][pointer.getCol() + 1].getStatus();
break;
case WEST:
s = buttons[pointer.getRow()][pointer.getCol() - 1].getStatus();
break;
default:
System.out.println("I don't know how you got here, somehow a nonexistent direction was called");
return false;
}
return s == OPEN || s == END || s == START;
} catch(ArrayIndexOutOfBoundsException e) {
return false;
}
}
private boolean isSolved() {
return end == pointer;
}
public void step() {
while(pointer != end) {
if(openMoves().isEmpty() && pointer != start) backtrack();
while(!openMoves().isEmpty()) {
for(Direction d : DIRECTIONS) {
if(pointer == end) return;
go(d);
}
}
}
}
private enum Direction {
NORTH, SOUTH, EAST, WEST;
Direction opposite() {
switch(this) {
case NORTH:
return SOUTH;
case SOUTH:
return NORTH;
case EAST:
return WEST;
case WEST:
return EAST;
default:
System.out.println("How did you even get here? There are only 4 directions.");
return null;
}
}
}
public static class FullPanel extends JPanel {
StackGUIMaze maze;
JButton start;
JPanel subPanel;
public FullPanel(int r, int c) {
maze = new StackGUIMaze(r, c);
setLayout(new BorderLayout());
add(maze, BorderLayout.CENTER);
JButton start = new JButton();
start.setText("Start");
start.addActionListener(e -> {
if(maze.getPointer() == null || maze.getEnd() == null) {
System.out.println("Could not solve, as the start/end is missing.");
} else {
for (Space[] sp : maze.buttons) {
for (Space s : sp) {
s.setEnabled(false);
}
}
maze.timer.start();
}
});
JButton setEnd = new JButton("Set end");
setEnd.addActionListener(e -> maze.mode = SET_END);
JButton setStart = new JButton("Set start");
setStart.addActionListener(e -> maze.mode = SET_START);
JButton buildButton = new JButton("Build maze");
buildButton.addActionListener(e -> maze.mode = BUILD);
subPanel = new JPanel();
subPanel.add(setStart);
subPanel.add(setEnd);
subPanel.add(buildButton);
subPanel.add(start);
add(subPanel, BorderLayout.SOUTH);
}
}
private class Branch extends Stack<Branch> {
Branch parent;
private Direction direction;
public Branch(Branch b, Direction d) {
parent = b;
direction = d;
}
public Direction getDirection() {
return direction;
}
public Branch getParent() {
return parent;
}
}
private class Space extends JButton {
int row;
int col;
private char status;
public Space(char status, int row, int col) {
this.status = status;
this.row = row;
this.col = col;
}
public char getStatus() {
return status;
}
public void setStatus(char s) {
char st = status;
this.status = s;
switch(s) {
case OPEN:
setBackground(Color.WHITE);
break;
case CLOSED:
setBackground(Color.BLACK);
break;
case TRIED:
if(st == FAILED) status = FAILED;
else setBackground(Color.LIGHT_GRAY);
break;
case FAILED:
setBackground(Color.RED);
break;
case SUCCEEDED:
setBackground(Color.GREEN);
break;
case POINTER:
if(pointer != null && !mode.equals(RUNNING)) pointer.setStatus(TRIED);
else if(pointer != null) pointer.setStatus(OPEN);
setBackground(Color.GRAY);
pointer = this;
break;
case END:
if(end != null) end.setStatus(OPEN);
setBackground(Color.CYAN);
end = this;
break;
case START:
if(start != null) pointer.setStatus(START);
setBackground(Color.MAGENTA);
start = this;
pointer = start;
break;
default:
System.out.println("Invalid status passed to method setStatus()");
}
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
}
}

我意识到它可能不是最优的,但我只想在开始改进它之前让它发挥作用

因此,您有两个基本问题:

  1. 您正在阻止ActionListener中的事件调度线程。直到actionPerformed方法返回后,UI才会更新
  2. 设置JButton的背景比其他组件稍微复杂一些。首先,我禁用了contentAreaFilledborderPainted属性,并设置了opaque按钮,使它们可以填充

例如。。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Example extends JPanel {
JButton[] buttons = new JButton[4];
public Example() {
setLayout(new GridLayout(1, 4));
for (int i = 0; i < 4; i++) {
add(buttons[i] = new JButton());
buttons[i].setContentAreaFilled(false);
buttons[i].setBorderPainted(false);
buttons[i].setOpaque(true);
}
}
public static class SubClass extends JPanel {
Example example;
JButton start;
private int counter = 0;
public SubClass() {
setLayout(new BorderLayout());
add(example = new Example(), BorderLayout.CENTER);
start = new JButton("Start");
start.addActionListener(e -> {
counter = 0;
Timer timer = new Timer(200, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (counter >= 4) {
((Timer) e.getSource()).stop();
return;
}
example.buttons[counter].setBackground(Color.BLACK);
counter++;
}
});
timer.start();
});
add(start, BorderLayout.SOUTH);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Title");
frame.setSize(500, 500);
frame.setLocation(500, 250);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new SubClass());
frame.setVisible(true);
}
});
}
}

最新更新