【问题标题】:Why does the thread freeze my code in my actionListener implementation?为什么线程在我的 actionListener 实现中冻结我的代码?
【发布时间】:2015-08-02 21:21:21
【问题描述】:

我试图通过点击按钮(向上,向下,...)使椭圆平滑移动,我尝试使用线程来尝试并捕获它在类中的工作,但是当我在这里使用它时,它会冻结整个应用程序。

class movingdown implements ActionListener{
    @Override 
    public void actionPerformed(ActionEvent e){
        for (int i = 0; i < frame.getWidth(); i++) {
            x++;
            frame.repaint();
            try{Thread.sleep(50);}catch(Exception ex){}

        }
    }
}//movingdown end

这是我的全部代码:

package swingWork;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;
public class movingball {
JFrame frame;
int x=0;
int y =0;
public static void main(String []args){
    movingball ball = new movingball();
    ball.start();

}
public void start(){
    ballDrawer drawer = new ballDrawer();   
    frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JButton up =new JButton("UP");
    JButton down =new JButton("DOWN");
    JButton left =new JButton("LEFT");
    JButton right =new JButton("RIGHT");

    frame.getContentPane().add(BorderLayout.NORTH, up);
    frame.getContentPane().add(BorderLayout.SOUTH, down);   
    frame.getContentPane().add(BorderLayout.WEST, left);
    frame.getContentPane().add(BorderLayout.EAST, right);
    frame.getContentPane().add(BorderLayout.CENTER,drawer);
    //setting up the action listener:

    up.addActionListener(new movingup());
    down.addActionListener(new movingdown());
    left.addActionListener(new movingleft());
    right.addActionListener(new movingright());
    frame.setSize(300, 300);
    frame.setVisible(true);
int rightsize =right.getWidth();



}//start method end


//here we have the listeners

class movingup implements ActionListener{

    @Override
    public void actionPerformed(ActionEvent e) {
    for (int i = x; i > frame.getHeight(); i--) {
        x=i;
        try{Thread.sleep(50);}catch(Exception ex){}
    }
    }

}//moving up end
class movingdown implements ActionListener{
    @Override 
    public void actionPerformed(ActionEvent e){
        for (int i = 0; i < frame.getWidth(); i++) {
            x++;
            frame.repaint();
            try{Thread.sleep(50);}catch(Exception ex){}

        }
    }
}//movingdown end
class movingleft implements ActionListener{
    public void actionPerformed(ActionEvent e){


            x=x+10;
            frame.repaint();

        System.out.println(x);

    }
}//moving  left end
class movingright implements ActionListener{
    public void actionPerformed(ActionEvent e){

    }
}//moving right end


class ballDrawer extends JPanel{
    @Override
public void paintComponent(Graphics g) {
        g.setColor(Color.white);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());

        g.setColor(Color.black);
        g.fillOval(x, y, 100, 100);


    }//PaintComponents End


}//ball drawer class end

}//main class end

【问题讨论】:

  • 因为 Swing 正忙于等待您的 ActionListener 返回,然后再执行其他任何操作。

标签: java multithreading swing try-catch actionlistener


【解决方案1】:

您永远不会想在Event Dispatch Thread 上使用Thread.sleep()。永远!!

它永远不会完成任何好事,它会让你的屏幕冻结,直到睡眠结束。所以不要这样做!

在 Swing 中制作动画的最佳方式是使用 javax.swing.Timer 来触发定期更改绘图属性的事件。这发生在不同的线程上,因此屏幕保持响应。您可以将此计时器设置为一直运行,但当圆圈不应该移动时,它什么也不做。这是TimerActionListener的代码

t = new Timer(TIMER_DELAY, new ActionListener() {
  @Override
  public void actionPerformed(ActionEvent e) {
    if(deltaX == 1 && x >= frame.getWidth() - CIRCLE_WIDTH) deltaX = 0;
    if(deltaY == 1 && y >= frame.getHeight() - CIRCLE_HEIGHT) deltaY = 0;
    if(deltaX == -1 && x <= 0) deltaX = 0;
    if(deltaY == -1 && y <= 0) deltaY = 0;
    x += deltaX;
    y += deltaY;
    if(deltaX != 0 || deltaY != 0) {
      frame.repaint();
    }
  }
});
t.start();

然后,您的按钮只需通过设置增量值来告诉计时器以正确的方向设置动画,例如

class movingup implements ActionListener {
  @Override
  public void actionPerformed(ActionEvent e) {
    deltaY = -1;
  }
}// moving up end

如您所见,我在start 方法中创建了一个计时器对象,并通过按下按钮告诉计时器开始动画。计时器有它自己的、不同的ActionListener(在此处实现为anonymous class),它实际上告诉参数要更改,然后如果有东西移动则重新绘制。您可以通过使用JComponent.repaint(Rectangle) 告诉 Swing 仅重绘需要重绘的屏幕部分来进一步优化它,但这只是一个示例。如果您这样做,请确保重绘足够大,以使 both 圆圈 所在的位置 和现在的位置。

这是完整的程序,包括常量CIRCLE_WIDTHCIRCLE_HEIGHT、新的计时器逻辑以及所有四个按钮都已更改:

package swingWork;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

public class movingball {
  private static final int CIRCLE_WIDTH = 100;
  private static final int CIRCLE_HEIGHT = 100;
  private static final int TIMER_DELAY = 50;

  JFrame frame;
  int x = 0;
  int y = 0;
  int deltaX = 0;
  int deltaY = 0;
  Timer t;

  public static void main(String[] args) {
    movingball ball = new movingball();
    ball.start();

  }

  public void start() {
    ballDrawer drawer = new ballDrawer();
    frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JButton up = new JButton("UP");
    JButton down = new JButton("DOWN");
    JButton left = new JButton("LEFT");
    JButton right = new JButton("RIGHT");

    frame.getContentPane().add(BorderLayout.NORTH, up);
    frame.getContentPane().add(BorderLayout.SOUTH, down);
    frame.getContentPane().add(BorderLayout.WEST, left);
    frame.getContentPane().add(BorderLayout.EAST, right);
    frame.getContentPane().add(BorderLayout.CENTER, drawer);
    // setting up the action listener:

    up.addActionListener(new movingup());
    down.addActionListener(new movingdown());
    left.addActionListener(new movingleft());
    right.addActionListener(new movingright());
    frame.setSize(300, 300);
    frame.setVisible(true);
    
    t = new Timer(TIMER_DELAY, new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        if(deltaX == 1 && x == frame.getWidth() - CIRCLE_WIDTH) deltaX = 0;
        if(deltaY == 1 && y == frame.getHeight() - CIRCLE_HEIGHT) deltaY = 0;
        if(deltaX == -1 && x == 0) deltaX = 0;
        if(deltaY == -1 && y == 0) deltaY = 0;
        x += deltaX;
        y += deltaY;
        if(deltaX != 0 || deltaY != 0) {
          frame.repaint();
        }
      }
    });
    t.start();

  }// start method end

  // here we have the listeners

  class movingup implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
      deltaY = -1;
    }
  }// moving up end

  class movingdown implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
      deltaY = 1;
    }
  }// movingdown end

  class movingleft implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
      deltaX = -1;
    }
  }// moving left end

  class movingright implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
      deltaX = 1;
    }
  }// moving right end

  class ballDrawer extends JPanel {
    @Override
    public void paintComponent(Graphics g) {
      super.paintComponent(g);
      g.setColor(Color.white);
      g.fillRect(0, 0, this.getWidth(), this.getHeight());

      g.setColor(Color.black);
      g.fillOval(x, y, CIRCLE_WIDTH, CIRCLE_HEIGHT);

    }// PaintComponents End

  }// ball drawer class end

}// main class endreading: http://stackoverflow.com/q/15511282/1768232

【讨论】:

  • 作为个人意见,我会更新位置,然后你是否进行范围检查,否则球可能会离开边界一帧;)
【解决方案2】:

正如已经指出的那样,您正在阻塞事件调度线程,这会阻止 UI 更新,直到您的循环和方法退出

更好的解决方案是使用 Swing Timer,它会在 EDT 的上下文中生成对 ActionListener 的常规回调,从而允许您从其中安全地更新 UI。请参阅Concurrency in SwingHow to use Swing Timers 了解更多详情

您还应该调用super.paintComponent,除其他外,它会自动为您填充Graphcis 上下文的背景。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
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 Movingball {

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

                Movingball ball = new Movingball();
                ball.start();
            }
        });
    }

    private BallDrawer drawer;

    public void start() {
        drawer = new BallDrawer();
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JButton up = new JButton("UP");
        JButton down = new JButton("DOWN");
        JButton left = new JButton("LEFT");
        JButton right = new JButton("RIGHT");

        frame.getContentPane().add(BorderLayout.NORTH, up);
        frame.getContentPane().add(BorderLayout.SOUTH, down);
        frame.getContentPane().add(BorderLayout.WEST, left);
        frame.getContentPane().add(BorderLayout.EAST, right);
        frame.getContentPane().add(BorderLayout.CENTER, drawer);
        // setting up the action listener:

        up.addActionListener(new Movingup());
        down.addActionListener(new Movingdown());
        left.addActionListener(new Movingleft());
        right.addActionListener(new Movingright());
        frame.pack();
        frame.setVisible(true);
    }// start method end

    // here we have the listeners
    class Movingup implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            drawer.moveUp();
        }
    }// moving up end

    class Movingdown implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            drawer.moveDown();
        }
    }// movingdown end

    class Movingleft implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            drawer.moveLeft();
        }
    }// moving left end

    class Movingright implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            drawer.moveRight();
        }
    }// moving right end

    public static class BallDrawer extends JPanel {

        public static final int CIRCLE_WIDTH = 100;
        public static final int CIRCLE_HEIGHT = 100;

        private int xDelta;
        private int yDelta;

        private int xPos;
        private int yPos;

        public BallDrawer() {
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    xPos += xDelta;
                    yPos += yDelta;

                    if (xPos < 0) {
                        xPos = 0;
                        xDelta = 0;
                    } else if (xPos + CIRCLE_WIDTH > getWidth()) {
                        xPos = getWidth() - CIRCLE_WIDTH;
                        xDelta = 0;
                    }
                    if (yPos < 0) {
                        yPos = 0;
                        yDelta = 0;
                    } else if (yPos + CIRCLE_HEIGHT > getHeight()) {
                        yPos = getHeight() - CIRCLE_HEIGHT;
                        yDelta = 0;
                    }

                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        public void moveLeft() {
            xDelta = -1;
        }

        public void moveRight() {
            xDelta = 1;
        }

        public void moveUp() {
            yDelta = -1;
        }

        public void moveDown() {
            yDelta = 1;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            g.setColor(Color.black);
            g.fillOval(xPos, yPos, CIRCLE_WIDTH, CIRCLE_HEIGHT);

        }// PaintComponents End

    }// ball drawer class end

}// main class end

您还应该将功能隔离到负责实际管理它的类,在这种情况下,您的 BallDrawer 应该管理球的运动,而不是其他类,这允许它是自包含和可重复使用的。

【讨论】:

  • 这个例子也将所有BallDrawer 的管理责任隔离给了类本身。这也是为什么这是一个社区帖子;)
【解决方案3】:

for 循环导致了很多延迟。尝试像这样重构您的代码。

public class MovingBall{
    String direction;
    int x=0;
    int y=0;

    public static void main(String[] args){
        //create your frame and buttons...
        upButton.addActionListener(new ActionListener{
            direction = "up";
        });
        //repeat this actionListener to the other buttons
        while (true){
            try{Thread.sleep(50);}catch(Exception e){e.printStackTrace();}
            drawer.repaint();
        }
    }
    public class Drawer extends JPanel{
        public void paint(Graphics g){
            if(direction.equals("up")) {
                y = y - 5;
            }
            //repeat the if statement with the other directions
            g.setColor(Color.white);
            g.fillRect(0,0,frame.getWidth(),frame.getHeight());
            g.setColor(Color.black);
            g.fillOval(x,y,100,100);
        }
    }
}

【讨论】:

  • 我没有在事件调度线程上调用 Thread.sleep() 方法...我在创建所有 ActionListener 之后调用它,并且我正在定期调用 Thread.sleep()刷新抽屉。 @durron597
  • 好吧,我已经纠正了,你没有在事件调度线程中调用它,但无论如何,这段代码不允许 JVM 优雅退出,它只会创建一个无限循环,会阻塞你的帧率。
  • 我在我的程序中使用这种方法,它可以实现平滑的帧速率,并且非常容易理解。它可能无法提供最佳帧率,但可以制作一个小球动画@durron597
  • 怎么知道调用了哪个线程main?您还打破了油漆链,作为一般建议,您不应该覆盖油漆
  • @MadProgrammer 我应该重写paintComponent 吗?
猜你喜欢
  • 1970-01-01
  • 2020-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多