简体   繁体   English

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

[英]JPanel doesn't update in real time when using delays

I am creating a GUI program to solve a maze. 我正在创建一个GUI程序来解决迷宫。 I already got the maze solving algorithm down, but weird stuff happens when I try to add a delay. 我已经得到了迷宫解决算法,但是当我尝试添加延迟时会发生奇怪的事情。

The code is pretty long, so here's something that does the same in terms of GUI delays. 代码很长,所以这里有一些在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);
    }
}

When I run the code without any delays, it works perfectly. 当我运行代码没有任何延迟时,它完美地工作。 However, whenever I try to add a delay, rather than the buttons updating one at a time, the whole window just freezes until all of the buttons are done updating, and then the entire thing updates at once. 但是,每当我尝试添加延迟时,而不是一次更新一个按钮,整个窗口就会冻结,直到所有按钮都完成更新,然后整个事件立即更新。

I tried using java.util.Timer , javax.swing.Timer , and Thread.sleep , but none of them seemed to work. 我尝试使用java.util.Timerjavax.swing.TimerThread.sleep ,但它们似乎都没有用。

Also, when I don't have the enclosing panel ( SubClass ) and just change the inner panel ( Example ) directly from the main method, it does work, and the buttons update in real time like they're supposed to. 此外,当我没有封闭面板( SubClass )并且只是直接从main方法更改内部面板( Example )时,它确实有效,并且按钮实时更新,就像它们应该的那样。

For example, if I changed the main method to 例如,如果我将main方法更改为

    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);
        }
    }

Looking back on it, I should have made the panels in reverse order, switching the subclass and the enclosing class, but at this point I'm done writing my program, and it works in all aspects except this one, so I don't want to do too much extra work and rewrite a bunch of stuff. 回想一下,我应该以相反的顺序制作面板,切换子类和封闭类,但此时我已经完成了我的程序编写,除了这个之外,它在所有方面都有效,所以我不这样做我想做太多额外的工作并重写一堆东西。 I also don't know if that would solve the problem anyway. 我也不知道这是否能解决问题。

Edit: Here is the full code: 编辑:这是完整的代码:

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;
        }
    }
}

I realize that it probably isn't optimal, but I just want to get it to work before I start trying to refine it. 我意识到它可能不是最佳的,但我只是想在开始尝试改进它之前让它工作。

So, you have two basic problems: 所以,你有两个基本问题:

  1. You're blocking the Event Dispatching Thread inside the ActionListener . 您正在阻止ActionListener的事件调度线程。 The UI won't be updated until after the actionPerformed method returns 直到actionPerformed方法返回后才会更新UI
  2. Setting the background of JButton is a little more complicated the other components. 设置JButton的背景比其他组件稍微复杂一些。 To start with, I disabled the contentAreaFilled and borderPainted properties and made the button opaque which allowed them to become filled. 首先,我禁用了contentAreaFilledborderPainted属性,并使按钮opaque ,这使得它们被填充。

For example.. 例如..

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);
            }
        });
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM