我正在使用 cardLayout 更改“ View ”(此类有一个 JFrame
变量)。当用户点击新游戏按钮时,会发生这种情况:
public class Views extends JFrame implements ActionListener {
private JFrame frame;
private CardLayout cl;
private JPanel cards;
private Game game;
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (command.equals("New game")) {
cl.show(cards, "Game");
game.init();
this.revalidate();
this.repaint();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
game.loop();
}
});
}
}
}
游戏的循环方式和类标题:
public class Game extends JPanel implements KeyListener {
public void loop() {
while (player.isAlive()) {
try {
this.update();
this.repaint();
// first class JFrame variable
jframee.getFrame().repaint();
// first class JFrame variable
jframee.getFrame().revalidate();
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void update() {
System.out.println("updated");
}
}
我正在使用 paintComponent()
绘画
public void paintComponent(Graphics g) {
System.out.println("paint");
...
}
其实它并没有画任何东西。当我不调用 loop()
方法(所以它只绘制一次)时,所有图像都被正确绘制。但是当我调用 loop()
方法时,窗口中什么也没有发生。 (即使 JFrame
上的关闭按钮也不起作用。)
如何解决? (当我在游戏类中创建 JFrame
时,一切正常,但现在我想要更多 View ,所以我需要在其他类中使用 JFrame
。)
谢谢。
最佳答案
前体:The Event Dispatch Thread (EDT) .
Swing 是单线程的。这是什么意思?
Swing 程序中的所有处理都以事件开始。 EDT 是一个线程,它按照以下几行(但更复杂)循环处理这些事件:
class EventDispatchThread extends Thread {
Queue<AWTEvent> queue = ...;
void postEvent(AWTEvent anEvent) {
queue.add(anEvent);
}
@Override
public void run() {
while (true) {
AWTEvent nextEvent = queue.poll();
if (nextEvent != null) {
processEvent(nextEvent);
}
}
}
void processEvent(AWTEvent theEvent) {
// calls e.g.
// ActionListener.actionPerformed,
// JComponent.paintComponent,
// Runnable.run,
// etc...
}
}
调度线程通过抽象对我们隐藏:我们通常只编写监听器回调。
- 单击一个按钮会发布一个事件 ( in native code ):处理该事件时,将在 EDT 上调用
actionPerformed
。 - 调用
repaint
会发布一个事件:处理该事件时,将在 EDT 上调用paintComponent
。 - 调用
invokeLater
发布一个事件:处理事件时,run
is called on the EDT . - Swing 中的一切都始于一个事件。
事件任务按发布顺序依次处理。
只有当当前事件任务返回时,才能处理下一个事件。这就是为什么我们不能在 EDT 上无限循环。 actionPerformed
(或 run
,在您的编辑中)永远不会返回,因此调用 repaint
post 绘制事件但是它们从未被处理过,程序似乎卡住了。
这就是“阻止”EDT 的意思。
基本上有两种方法可以在 Swing 程序中制作动画:
使用
Thread
(或SwingWorker
)。使用线程的好处是处理是关闭 EDT 完成的,因此如果有密集处理,GUI 仍然可以并发更新。
使用
javax.swing.Timer
.使用计时器的好处是处理是在 EDT 上完成的,因此无需担心同步,并且可以安全地更改 GUI 组件的状态。
一般来说,如果有理由不使用定时器,我们应该只在 Swing 程序中使用线程。
对于用户来说,它们之间没有明显的区别。
您对 revalidate
的调用向我表明您正在修改循环中组件的状态(添加、删除、更改位置等)。这对于关闭 EDT 来说不一定是安全的。如果您正在修改组件的状态,那么使用计时器而不是线程是一个令人信服的理由。使用没有适当同步的线程会导致难以诊断的细微错误。参见 Memory Consistency Errors .
在某些情况下,组件上的操作是在 tree lock 下完成的(Swing 确保它们本身是线程安全的),但在某些情况下它们不是。
我们可以将一个循环变成如下形式:
while ( condition() ) {
body();
Thread.sleep( time );
}
进入以下形式的 Timer
:
new Timer(( time ), new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
if ( condition() ) {
body();
} else {
Timer self = (Timer) evt.getSource();
self.stop();
}
}
}).start();
这是一个简单的示例,演示了同时使用线程和计时器的动画。绿色条在黑色面板上循环移动。
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class SwingAnimation implements Runnable{
public static void main(String[] args) {
SwingUtilities.invokeLater(new SwingAnimation());
}
JToggleButton play;
AnimationPanel animation;
@Override
public void run() {
JFrame frame = new JFrame("Simple Animation");
JPanel content = new JPanel(new BorderLayout());
play = new JToggleButton("Play");
content.add(play, BorderLayout.NORTH);
animation = new AnimationPanel(500, 50);
content.add(animation, BorderLayout.CENTER);
// 'true' to use a Thread
// 'false' to use a Timer
if (false) {
play.addActionListener(new ThreadAnimator());
} else {
play.addActionListener(new TimerAnimator());
}
frame.setContentPane(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
abstract class Animator implements ActionListener {
final int period = ( 1000 / 60 );
@Override
public void actionPerformed(ActionEvent ae) {
if (play.isSelected()) {
start();
} else {
stop();
}
}
abstract void start();
abstract void stop();
void animate() {
int workingPos = animation.barPosition;
++workingPos;
if (workingPos >= animation.getWidth()) {
workingPos = 0;
}
animation.barPosition = workingPos;
animation.repaint();
}
}
class ThreadAnimator extends Animator {
volatile boolean isRunning;
Runnable loop = new Runnable() {
@Override
public void run() {
try {
while (isRunning) {
animate();
Thread.sleep(period);
}
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
};
@Override
void start() {
isRunning = true;
new Thread(loop).start();
}
@Override
void stop() {
isRunning = false;
}
}
class TimerAnimator extends Animator {
Timer timer = new Timer(period, new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
animate();
}
});
@Override
void start() {
timer.start();
}
@Override
void stop() {
timer.stop();
}
}
static class AnimationPanel extends JPanel {
final int barWidth = 10;
volatile int barPosition;
AnimationPanel(int width, int height) {
setPreferredSize(new Dimension(width, height));
setBackground(Color.BLACK);
barPosition = ( width / 2 ) - ( barWidth / 2 );
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
int currentPos = barPosition;
g.setColor(Color.GREEN);
g.fillRect(currentPos, 0, barWidth, height);
if ( (currentPos + barWidth) >= width ) {
g.fillRect(currentPos - width, 0, barWidth, height);
}
}
}
}
关于Java 游戏循环(绘画)卡住了我的窗口,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29745778/