我正在改变"观点" with cardLayout(此类有一个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
时,一切正常,但现在我希望有更多的视图,所以我需要在其他课程中JFrame
。)
感谢。
答案 0 :(得分:3)
前体: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...
}
}
调度线程通过抽象对我们隐藏:我们通常只编写监听器回调。
actionPerformed
。repaint
发布事件:处理事件时,在EDT上调用paintComponent
。invokeLater
发布活动:处理活动时run
is called on the EDT。按照发布的顺序依次处理事件任务。
只有当前事件任务返回时才能处理下一个事件。这就是为什么我们不能在EDT上有一个无限循环。 actionPerformed
(或编辑中的run
)永远不会返回,因此调用repaint
发布绘制事件,但它们永远不会被处理并且程序似乎冻结了。
这就是&#34;阻止&#34;美国东部时间。
在Swing程序中基本上有两种方法可以做动画:
使用Thread
(或SwingWorker
)。
使用线程的好处是处理是在关闭 EDT完成的,所以如果进行密集处理,GUI仍然可以同时更新。
使用计时器的好处是处理在 on 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);
}
}
}
}
答案 1 :(得分:2)
更新有什么作用?您可能不应该在EDT上致电var_dump($prices[105]);
# array(1) {
# [0]=>
# int(7)
# }
。你正在EDT上运行一个循环,你的重绘不会被调用,因为重绘在EDT上排队一个事件,它似乎很忙。尝试将game.loop()
移动到另一个帖子
game.loop()
这样你就不会阻止EDT,而重绘仍然在EDT上执行。
答案 2 :(得分:1)
将game.loop()方法调用移动到类似:
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
game.loop()
}
});
感谢。