【问题标题】:Button triggered repaint() not working while call the function directly works fine直接调用函数时按钮触发的重绘()不起作用
【发布时间】:2015-11-29 21:31:37
【问题描述】:

我正在使用递归创建动画,展示河内塔的过程。 这是我的完整代码,我的问题是当我使用 main 调用 move() 函数时,一切正常,但是当我尝试使用按钮时,它不起作用。指示move() 的控制台输出确实被调用,但窗口没有被刷新。真的卡在这里了,非常感谢帮助。

public class TowerOfHanoi<T>{

//empty constructor, just for calling initializing methods
public TowerOfHanoi(){

}

private int numberOfLayers;

private Object[] towerA;
private Object[] towerB;
private Object[] towerC;

public int towerHeightOf(Object[] theTower){
    int result =0;
    for(Object current: theTower){
        if(current != null){
            result++;
        }
    }
    return result;
}


// initialize the arrays that holds the integer that represent the plate in tower
public void setLayerNbr(int layerNbr){
    this.numberOfLayers = layerNbr;
    this.moveCount = 0;

    towerA = new Object[numberOfLayers];
    towerB = new Object[numberOfLayers];
    towerC = new Object[numberOfLayers];

    //initialize the tower A
    for(int i=0; i< numberOfLayers; i++){
        towerA[i] = new Integer(layerNbr--);
    }
}

//these are the plate shifting method
//return the top most element of the tower
//warning, you are not only get the top plate, but remove it from the original racket as well
//specifications is the hardest stuff
@SuppressWarnings("unchecked")
public T getTopPlate(Object[] tower){
    Object lastElement = null;
    if(tower[tower.length-1] != null){
        lastElement = tower[tower.length-1];
    }
    for(int i=tower.length-1; i>-1; i--){
        if(tower[i] != null){
            lastElement = tower[i];
            tower[i] = null;
            break;
        }
    }
    return (T) lastElement;
}

// add a plate to a tower
public void addToTop(T currentPlate, Object[] targetTower){
    for(int i=0; i< numberOfLayers; i++){
        if(targetTower[i] == null){
            targetTower[i] = currentPlate;
            return;
        }
    }
}

// move the top plate from one to another
public void movePlate(Object[] originalTower, Object[] targetTower){
    addToTop(getTopPlate(originalTower), targetTower);
}

public Object[] getTowerByName(char name){
    Object[] result = null;
    switch(name){
    case 'A':
        result = towerA;
        break;

    case 'B':
        result = towerB;
        break;

    case 'C':
        result = towerC;
        break;
    default:
        break;

    }
    return result;
}

public void showTower(char name){
    Object[] currentTower = getTowerByName(name);
    for(int i=currentTower.length-1; i>-1; i--){
        if(currentTower[i] != null){
            System.out.println(currentTower[i].toString());
        }else{
            return;
        }
    }
}

/**
 * in most scenarios, nested components plus layout manager can achieve what you want
 */

private int moveCount;

///////
///////
///////
///////



private JFrame windowFrame; // top level container

private JPanel promptPanel; // contains the following two panels
private JLabel messageLabel;
private JLabel moveCountLabel;

private JPanel animationPanel;

private JPanel bottomeStaticPanel;// contains the following two panels

private JPanel buttonPanel; // contains the following
private JButton startBtn;
private JButton pauseBtn;
private JButton quitBtn;

private JPanel racketPanel;         // contains the following 3 panels, using grid panel
private JLabel aLabel = new JLabel("A");
private JLabel bLabel = new JLabel("B");
private JLabel cLabel = new JLabel("C");



//display the static background of the window
public void display(){

    windowFrame = new JFrame("Tower of Hanoi");
    windowFrame.setSize(1200, 700);
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    windowFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    windowFrame.setBackground(Color.white);
    Container frameContainer = windowFrame.getContentPane();

    //set up the prompt panel
    promptPanel = new JPanel(new BorderLayout());
    messageLabel = new JLabel("Click \"Start\" to see the move!                          ");
    moveCountLabel = new JLabel("Moves Count: "+ moveCount+"    ");
    Font messageFont = new Font(Font.SANS_SERIF, Font.BOLD, 20);
    Font moveCountFont = new Font(Font.DIALOG, Font.BOLD, 15);
    messageLabel.setFont(messageFont);
    moveCountLabel.setFont(moveCountFont);
    messageLabel.setHorizontalAlignment(SwingConstants.CENTER);
    moveCountLabel.setHorizontalAlignment(SwingConstants.LEFT);
    promptPanel.add(messageLabel,BorderLayout.CENTER);
    promptPanel.add(moveCountLabel,BorderLayout.EAST);

    //set up the animation panel
    // todo: back ground color to be changed
    animationPanel = new JPanel();
    animationPanel.setPreferredSize(new Dimension(1200, 700));
    animationPanel.setBackground(Color.lightGray);
    animationPanel.setLayout(new BorderLayout());


    //set up the racket panel 
    aLabel = new JLabel("Tower A");
    bLabel = new JLabel("Tower B");
    cLabel = new JLabel("Tower C");
    Font racketFont = new Font(Font.SERIF, Font.BOLD, 17);
    aLabel.setFont(racketFont);
    bLabel.setFont(racketFont);
    cLabel.setFont(racketFont);
    aLabel.setHorizontalAlignment(SwingConstants.CENTER);
    bLabel.setHorizontalAlignment(SwingConstants.CENTER);
    cLabel.setHorizontalAlignment(SwingConstants.CENTER);

    racketPanel = new JPanel(new GridLayout(1, 3));
    racketPanel.add(aLabel);
    racketPanel.add(bLabel);
    racketPanel.add(cLabel);
    racketPanel.setBackground(Color.lightGray);

    //set up the button panel
    buttonPanel = new JPanel();
    startBtn = new JButton("Start");
    pauseBtn = new JButton("Pause");
    quitBtn = new JButton("Quit");


    startBtn.setEnabled(true);
    pauseBtn.setEnabled(false);
    quitBtn.setEnabled(true);

    buttonPanel.add(startBtn);
    buttonPanel.add(pauseBtn);
    buttonPanel.add(quitBtn);

    //set up the bottom panel
    bottomeStaticPanel = new JPanel(new BorderLayout());
    bottomeStaticPanel.add(racketPanel, BorderLayout.NORTH);
    bottomeStaticPanel.add(buttonPanel, BorderLayout.SOUTH);

    //set up the whole window frame
    frameContainer.add(promptPanel,BorderLayout.NORTH);
    frameContainer.add(animationPanel, BorderLayout.CENTER);
    frameContainer.add(bottomeStaticPanel, BorderLayout.SOUTH);

    //position the window frame on screen and display it
    windowFrame.setLocation(dim.width/2-windowFrame.getSize().width/2, dim.height/2-windowFrame.getSize().height/2);
    windowFrame.setVisible(true);

    ///*******////
    ///*******////
    ///*******////
    ///*******////
    //NOTE: since the constructor of the paintable panel needs animation panel's size, we need to make the JFrame render first
    PaintablePanel canvas = new PaintablePanel();// the panel actually do the animation

    animationPanel.add(canvas, BorderLayout.CENTER); //make the canvas take up the entire animation panel
    //Why make this nested structure, this way we can get size of the container.

    //this is vital, you must repaint after adding something to the container?
    //Other wise the change will not show up
    windowFrame.revalidate();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

public void setUpActionListeners(){
    startBtn.addActionListener(new startBtnListener());
    pauseBtn.addActionListener(new pauseBtnListener());
    quitBtn.addActionListener(new quitBtnListener());
}


@SuppressWarnings("serial")
class PaintablePanel extends JPanel{

    //dimension constant
    private final int HORIZONTAL_UNIT = animationPanel.getWidth()/1200; // 10 pixel per unit
    private final int PLATE_THICKNESS = animationPanel.getHeight()/(numberOfLayers+1);
    private final int BORDER_WIDTH = 2;
    private final int VERTICAL_BAR_STARTING_X = HORIZONTAL_UNIT*200-5;
    private final int BAR_HEIGHT =PLATE_THICKNESS*(numberOfLayers+1) ;
    private final int VERTICAL_BAR_STARTING_Y = animationPanel.getHeight()-BAR_HEIGHT;
    private final int PLATE_WIDTH_UNIT = 300*HORIZONTAL_UNIT/numberOfLayers;

    //color constant
    private final Color PLATE_COLOR = Color.CYAN;
    private final Color PLATE_BORDER_COLOR = Color.DARK_GRAY;
    private final Color VERTICAL_BAR_COLOR = Color.black;
    private final Color CHARACTER_COLOR = Color.blue;



    @Override
    public void paint(Graphics g){
        //todo : paint three towers according to its current status
        //paint the vertical bar;
        g.setColor(VERTICAL_BAR_COLOR);
        for(int i=0; i<3;i++){
            g.fillRect(VERTICAL_BAR_STARTING_X+i*400*HORIZONTAL_UNIT
                    ,VERTICAL_BAR_STARTING_Y
                    ,10
                    ,BAR_HEIGHT);
        }
        //debug
        int AHeight = towerHeightOf(towerA);
        int BHeight = towerHeightOf(towerB);
        int CHeight = towerHeightOf(towerC);



        for(int i=0; i<AHeight; i++){
            Integer currentPlate = (Integer)towerA[i];
            //NOTE: cast to double then cast back to int, make sure there is always something to display
            int outterX= (int) (200*HORIZONTAL_UNIT - ((double)currentPlate.intValue())/2*PLATE_WIDTH_UNIT);
            int outterY= animationPanel.getHeight() - PLATE_THICKNESS*(i+1); //the shifting amount of outterY relates to the layers accumulated
            int outterWidth = currentPlate.intValue()*PLATE_WIDTH_UNIT;
            int outterHeight = PLATE_THICKNESS;
            int innerX = outterX+BORDER_WIDTH;
            int innerY = outterY+BORDER_WIDTH;
            int innerWidth = outterWidth - 2*BORDER_WIDTH;
            int innerHeight = outterHeight - 2*BORDER_WIDTH;
            int centerX = 200*HORIZONTAL_UNIT;
            int centerY = outterY+PLATE_THICKNESS/2;
            Font characterFont = new Font(Font.SERIF, Font.BOLD, PLATE_THICKNESS/4);
            g.setColor(PLATE_BORDER_COLOR);
            g.fillRect(outterX, outterY, outterWidth, outterHeight);
            g.setColor(PLATE_COLOR);
            g.fillRect(innerX, innerY, innerWidth, innerHeight);
            g.setColor(CHARACTER_COLOR);
            g.setFont(characterFont);
            g.drawString(currentPlate.toString(), centerX-characterFont.getSize()/3, centerY+characterFont.getSize()/4);
        }

        for(int i=0; i<BHeight; i++){
            Integer currentPlate = (Integer)towerB[i];
            //NOTE: cast to double then cast back to int, make sure there is always something to display
            int outterX= (int) (600*HORIZONTAL_UNIT - ((double)currentPlate.intValue())/2*PLATE_WIDTH_UNIT);
            int outterY= animationPanel.getHeight() - PLATE_THICKNESS*(i+1); //the shifting amount of outterY relates to the layers accumulated
            int outterWidth = currentPlate.intValue()*PLATE_WIDTH_UNIT;
            int outterHeight = PLATE_THICKNESS;
            int innerX = outterX+BORDER_WIDTH;
            int innerY = outterY+BORDER_WIDTH;
            int innerWidth = outterWidth - 2*BORDER_WIDTH;
            int innerHeight = outterHeight - 2*BORDER_WIDTH;
            g.setColor(PLATE_BORDER_COLOR);
            g.fillRect(outterX, outterY, outterWidth, outterHeight);
            g.setColor(PLATE_COLOR);
            g.fillRect(innerX, innerY, innerWidth, innerHeight);
            int centerX = 600*HORIZONTAL_UNIT;
            int centerY = outterY+PLATE_THICKNESS/2;
            Font characterFont = new Font(Font.SERIF, Font.BOLD, PLATE_THICKNESS/4);
            g.setColor(CHARACTER_COLOR);
            g.setFont(characterFont);
            g.drawString(currentPlate.toString(), centerX-characterFont.getSize()/3, centerY+characterFont.getSize()/4);
        }

        for(int i=0; i<CHeight; i++){
            Integer currentPlate = (Integer)towerC[i];
            //NOTE: cast to double then cast back to int, make sure there is always something to display
            int outterX= (int) (1000*HORIZONTAL_UNIT - ((double)currentPlate.intValue())/2*PLATE_WIDTH_UNIT);
            int outterY= animationPanel.getHeight() - PLATE_THICKNESS*(i+1); //the shifting amount of outterY relates to the layers accumulated
            int outterWidth = currentPlate.intValue()*PLATE_WIDTH_UNIT;
            int outterHeight = PLATE_THICKNESS;
            int innerX = outterX+BORDER_WIDTH;
            int innerY = outterY+BORDER_WIDTH;
            int innerWidth = outterWidth - 2*BORDER_WIDTH;
            int innerHeight = outterHeight - 2*BORDER_WIDTH;
            g.setColor(PLATE_BORDER_COLOR);
            g.fillRect(outterX, outterY, outterWidth, outterHeight);
            g.setColor(PLATE_COLOR);
            g.fillRect(innerX, innerY, innerWidth, innerHeight);
            int centerX = 1000*HORIZONTAL_UNIT;
            int centerY = outterY+PLATE_THICKNESS/2;
            Font characterFont = new Font(Font.SERIF, Font.BOLD, PLATE_THICKNESS/4);
            g.setColor(CHARACTER_COLOR);
            g.setFont(characterFont);
            g.drawString(currentPlate.toString(), centerX-characterFont.getSize()/3, centerY+characterFont.getSize()/4);
        }

    }
}



/**
 * This method prints out the steps to achieve Hanoi Tower
 * 
 * @param numberOfPlates  how many plates are in the number
 * @param homeStack       
 * @param aimStack
 * @param mediaStack
 */
public void move(int numberOfPlates, char homeStack
        , char targetStack
        , char mediaStack ){


    if(numberOfPlates == 1){
        //debug
        //debug
        windowFrame.revalidate();
        windowFrame.repaint();
        System.out.println("repaint() get called");


        System.out.println("Move "+ numberOfPlates+" from Stack \'"+ homeStack+"\' to "+"Stack \'"+targetStack+"\'");
        //Truly needed
        messageLabel.setText("Move "+ numberOfPlates+" from Stack \'"+ homeStack+"\' to "+"Stack \'"+targetStack+"\'");
        movePlate(getTowerByName(homeStack), getTowerByName(targetStack));
        moveCount++;
        moveCountLabel.setText("Moves Count: "+ moveCount+"         ");

        //delay the move step for one second
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return;
    }


    move(numberOfPlates-1, homeStack, mediaStack, targetStack);


    //debug
    windowFrame.revalidate();
    windowFrame.repaint();

    System.out.println("Move "+ numberOfPlates+" from Stack \'"+ homeStack+"\' to "+"Stack \'"+targetStack+"\'");
    //debug
    messageLabel.setText("Move "+ numberOfPlates+" from Stack \'"+ homeStack+"\' to "+"Stack \'"+targetStack+"\'");
    movePlate(getTowerByName(homeStack), getTowerByName(targetStack));

    moveCount++;
    moveCountLabel.setText("Moves Count: "+ moveCount+"         ");

    //delay the move step
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }



    move(numberOfPlates-1, mediaStack, targetStack, homeStack);
}


class startBtnListener implements ActionListener{

    @Override
    public void actionPerformed(ActionEvent e) {
        // TODO Auto-generated method stub
        startBtn.setEnabled(false);
        pauseBtn.setEnabled(true);
        quitBtn.setEnabled(true);

        windowFrame.revalidate();
        windowFrame.repaint();

        System.out.println("move started");
        move(numberOfLayers, 'A', 'C', 'B');
        System.out.println("move finished");
    }

}

// to be fixed
class pauseBtnListener implements ActionListener{

    @Override
    public void actionPerformed(ActionEvent e) {
        // TODO Auto-generated method stub
        String status = "";
        if(pauseBtn.getText() == "Pause"){
            pauseBtn.setText("Resume");
            status = "Resume";
        }else{
            pauseBtn.setText("Pause");
        }

        //hang up the current thread
        while(status != "Resume"){

            try {
                Thread.sleep(250);
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }

    }

}

class quitBtnListener implements ActionListener{

    @Override
    public void actionPerformed(ActionEvent e) {
        // TODO Auto-generated method stub
        userChoseQuit();
    }

}




////////////////
////////////////
////////////////


//check if the input string represent a valid integer
public boolean isAValidInteger(String rawInput) {
    try {
        Integer.parseInt(rawInput);// throw exception if rawInput is not
        // integer
        return true;

    } catch (NumberFormatException e) {
        JOptionPane.showMessageDialog(null, "Input must be an integer",
                "Invalid input", JOptionPane.INFORMATION_MESSAGE);
        return false;
    }
}

// This method show a message if the user click cancel during the execution
// of the program
// and terminate the program
public void userChoseQuit() {
    JOptionPane.showMessageDialog(null,
            "Program terminated by user before finish execution.",
            "Program exit successfully, good bye! ", JOptionPane.INFORMATION_MESSAGE);
    System.exit(0);
}

//recursively validating the input
public int getInput(){
    String userInputLayersNbr = JOptionPane.showInputDialog(null,
            "Please Enter the Number Of Layers of Hanoi Tower:",
            "Wecome To Tower Of Hanoi Animation", JOptionPane.QUESTION_MESSAGE);

    if(userInputLayersNbr == null){
        this.userChoseQuit();
        return -1;
    }else{
        if(this.isAValidInteger(userInputLayersNbr)){
            int layerNbr = Integer.parseInt(userInputLayersNbr);
            if(layerNbr >= 1){
                return layerNbr;
            }else{
                JOptionPane.showMessageDialog(null,
                        "Number of layers must be an integer greater than 0",
                        "Try Again Please", JOptionPane.WARNING_MESSAGE);

                return this.getInput();
            }
        }
        return this.getInput();
    }

}

/////////////////
/////////////////
/////////////////



public static void main(String[] args){


    TowerOfHanoi<Integer> theTower = new TowerOfHanoi<>();
    int layerNbr =theTower.getInput();
    theTower.setLayerNbr(layerNbr);

    theTower.display();
    theTower.setUpActionListeners();
    //let us use the button to call move now
    //theTower.move(layerNbr, 'A', 'C', 'B');

    return;

}

}

【问题讨论】:

标签: java swing animation awt actionlistener


【解决方案1】:

当我使用 main 调用 move() 函数时,一切正常

从您的 main() 方法调用的代码在单独的线程上运行(顺便说一句,这是错误的,因为所有 GUI 代码都应该在事件调度线程 (EDT) 上创建。

当我尝试使用按钮时,它不起作用

在侦听器中执行的代码在 EDT 上调用,EDT 是负责重新绘制 GUI 的线程。由于您有一个 Thread.sleep(),这将导致 EDT 休眠,这意味着 GUI 无法自行重绘。

阅读 Concurrency 上的 Swing 教程部分,了解有关此概念的更多信息。

解决方案是为长时间运行的任务使用单独的线程,然后当您使线程休眠时,它不会影响 GUI。

或者也许你使用 Swing Timer 来安排动画,所以你不需要使用 Thread.sleep。在任何情况下,代码都需要重组。阅读教程。本教程还有一个关于计时器的部分。

另外,您不应该覆盖paint() 方法。自定义绘画是通过覆盖面板的 paintComponent() 方法完成的,不要忘记调用 super.paintComponent() 来清除背景。

【讨论】:

  • 非常感谢!老实说,我不知道线程到底是什么。试着玩一下新学的 GUI 东西和一些算法。在理解之前,我不应该复制和粘贴Thread.sleep() 的东西。我正在赶上我的读数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-11
  • 2016-12-05
  • 1970-01-01
  • 2022-01-20
  • 2014-03-25
  • 2013-01-08
  • 2016-08-19
相关资源
最近更新 更多