为什么这个简单的循环会导致我的 JFrame 出现问题行为
Why this Simple Loop is Causing Problematic Behavior of my JFrame
我正在做 2007 年编写的 Java Exposure 教科书的作业。这本书包含一些代码,我通常会更新这些代码以使用一些较新的功能(只是基本的东西)。但是,在这个问题中我 运行 遇到了问题。我试图做的就是将 show
替换为 setVisible(true)
并将 Frame
更改为 JFrame
并添加 gfx.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
。但是,我注意到这实际上不会导致 window 关闭。如果我点击多次,也许 1/30 次尝试就会关闭。如果我将延迟从 10 减少到 1,它通常会在 2 次尝试内关闭。这当然让我相信 delay
方法导致了这种不稳定的行为。我尝试了 Thread.sleep
,但当然没有用。 有没有什么简单的方法可以获取此代码,以便在我点击关闭按钮时关闭框架?如果没有,那么不太简单的方法是什么?
代码如下:
// Lab30st.java
// The Screen Saver Program
// Student Version
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import javax.swing.JOptionPane;
public class Lab30st
{
public static void main(String args[])
{
GfxApp gfx = new GfxApp();
gfx.setSize(800,600);
gfx.addWindowListener(new WindowAdapter() {public void
windowClosing(WindowEvent e) {System.exit(0);}});
gfx.show();
}
}
class GfxApp extends Frame
{
private int circleCount, circleSize;
public GfxApp()
{
circleCount = 50;
circleSize = 30;
}
class Coord
{
private int xPos;
private int yPos;
public Coord(int x, int y)
{
xPos = x;
yPos = y;
}
}
public void paint(Graphics g)
{
int incX = 5;
int incY = 5;
int diameter = 30;
int timeDelay = 10;
Circle c = new Circle(g,diameter,incX,incY,timeDelay);
for (int k = 1; k <= 2000; k++)
{
c.drawCircle(g);
c.hitEdge();
}
}
}
class Circle
{
private int tlX; // top-left X coordinate
private int tlY; // top-left Y coordinate
private int incX; // increment movement of X coordinate
private int incY; // increment movement of Y coordinate
private boolean addX; // flag to determine add/subtract of increment for X
private boolean addY; // flag to determine add/subtract of increment for Y
private int size; // diameter of the circle
private int timeDelay; // time delay until next circle is drawn
public Circle(Graphics g, int s, int x, int y, int td)
{
incX = x;
incY = y;
size = s;
addX = true;
addY = false;
tlX = 400;
tlY = 300;
timeDelay = td;
}
public void delay(int n)
{
long startDelay = System.currentTimeMillis();
long endDelay = 0;
while (endDelay - startDelay < n)
endDelay = System.currentTimeMillis();
}
public void drawCircle(Graphics g)
{
g.setColor(Color.blue);
g.drawOval(tlX,tlY,size,size);
delay(timeDelay);
if (addX)
tlX+=incX;
else
tlX-=incX;
if (addY)
tlY+=incY;
else
tlY-=incY;
}
public void newData()
{
incX = (int) Math.round(Math.random() * 7 + 5);
incY = (int) Math.round(Math.random() * 7 + 5);
}
public void hitEdge()
{
boolean flag = false;
if (tlX < incX)
{
addX = true;
flag = true;
}
if (tlX > 800 - (30 + incX))
{
addX = false;
flag = true;
}
if (tlY < incY + 30) // The +30 is due to the fact that the title bar covers the top 30 pixels of the window
{
addY = true;
flag = true;
}
if (tlY > 600 - (30 + incY))
{
addY = false;
flag = true;
}
if (flag)
newData();
}
}
您是"freezing"
的事件调度线程
public void delay(int n)
{
long startDelay = System.currentTimeMillis();
long endDelay = 0;
while (endDelay - startDelay < n)
endDelay = System.currentTimeMillis();
}
这意味着所有其他试图发生的事情(比如关闭 window)必须等到线程从 "sleep" 出来。
基本上你不应该在 EDT 中做延迟,它应该在不同的线程上,然后要求 EDT 线程更新。
您的 "busy wait" 延迟也可能导致其他问题。您可以使用 Thread.sleep()
改善行为
见Java Event-Dispatching Thread explanation
太糟糕了。
您需要重构整个代码。
让我们从最糟糕的开始:
delay
是(几乎)一个繁忙的等待,自从 BASIC 是现代的以来,我还没有看到繁忙的等待。它基本上将 CPU 劫持到线程,它不仅什么也不做,其他线程(几乎)都不能使用时间片。之所以说几乎是因为调用系统时间函数会导致上下文切换,可以让其他线程运行,但还是不好
仍然很糟糕:
替换为 Thread.sleep
。更好的是,没有忙碌的等待,但你仍然持有唯一的 UI 线程。这意味着在关闭主 window.
之前不会发生其他 UI 工作
需要发生什么:
获取外部计时器(例如javax.swing.Timer
)以触发绘图事件并执行动画的下一部分。
搜索 "Java smooth animation" 有很多关于如何执行此操作的示例,双缓冲区等等。
我正在做 2007 年编写的 Java Exposure 教科书的作业。这本书包含一些代码,我通常会更新这些代码以使用一些较新的功能(只是基本的东西)。但是,在这个问题中我 运行 遇到了问题。我试图做的就是将 show
替换为 setVisible(true)
并将 Frame
更改为 JFrame
并添加 gfx.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
。但是,我注意到这实际上不会导致 window 关闭。如果我点击多次,也许 1/30 次尝试就会关闭。如果我将延迟从 10 减少到 1,它通常会在 2 次尝试内关闭。这当然让我相信 delay
方法导致了这种不稳定的行为。我尝试了 Thread.sleep
,但当然没有用。 有没有什么简单的方法可以获取此代码,以便在我点击关闭按钮时关闭框架?如果没有,那么不太简单的方法是什么?
代码如下:
// Lab30st.java
// The Screen Saver Program
// Student Version
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import javax.swing.JOptionPane;
public class Lab30st
{
public static void main(String args[])
{
GfxApp gfx = new GfxApp();
gfx.setSize(800,600);
gfx.addWindowListener(new WindowAdapter() {public void
windowClosing(WindowEvent e) {System.exit(0);}});
gfx.show();
}
}
class GfxApp extends Frame
{
private int circleCount, circleSize;
public GfxApp()
{
circleCount = 50;
circleSize = 30;
}
class Coord
{
private int xPos;
private int yPos;
public Coord(int x, int y)
{
xPos = x;
yPos = y;
}
}
public void paint(Graphics g)
{
int incX = 5;
int incY = 5;
int diameter = 30;
int timeDelay = 10;
Circle c = new Circle(g,diameter,incX,incY,timeDelay);
for (int k = 1; k <= 2000; k++)
{
c.drawCircle(g);
c.hitEdge();
}
}
}
class Circle
{
private int tlX; // top-left X coordinate
private int tlY; // top-left Y coordinate
private int incX; // increment movement of X coordinate
private int incY; // increment movement of Y coordinate
private boolean addX; // flag to determine add/subtract of increment for X
private boolean addY; // flag to determine add/subtract of increment for Y
private int size; // diameter of the circle
private int timeDelay; // time delay until next circle is drawn
public Circle(Graphics g, int s, int x, int y, int td)
{
incX = x;
incY = y;
size = s;
addX = true;
addY = false;
tlX = 400;
tlY = 300;
timeDelay = td;
}
public void delay(int n)
{
long startDelay = System.currentTimeMillis();
long endDelay = 0;
while (endDelay - startDelay < n)
endDelay = System.currentTimeMillis();
}
public void drawCircle(Graphics g)
{
g.setColor(Color.blue);
g.drawOval(tlX,tlY,size,size);
delay(timeDelay);
if (addX)
tlX+=incX;
else
tlX-=incX;
if (addY)
tlY+=incY;
else
tlY-=incY;
}
public void newData()
{
incX = (int) Math.round(Math.random() * 7 + 5);
incY = (int) Math.round(Math.random() * 7 + 5);
}
public void hitEdge()
{
boolean flag = false;
if (tlX < incX)
{
addX = true;
flag = true;
}
if (tlX > 800 - (30 + incX))
{
addX = false;
flag = true;
}
if (tlY < incY + 30) // The +30 is due to the fact that the title bar covers the top 30 pixels of the window
{
addY = true;
flag = true;
}
if (tlY > 600 - (30 + incY))
{
addY = false;
flag = true;
}
if (flag)
newData();
}
}
您是"freezing"
的事件调度线程public void delay(int n)
{
long startDelay = System.currentTimeMillis();
long endDelay = 0;
while (endDelay - startDelay < n)
endDelay = System.currentTimeMillis();
}
这意味着所有其他试图发生的事情(比如关闭 window)必须等到线程从 "sleep" 出来。 基本上你不应该在 EDT 中做延迟,它应该在不同的线程上,然后要求 EDT 线程更新。
您的 "busy wait" 延迟也可能导致其他问题。您可以使用 Thread.sleep()
见Java Event-Dispatching Thread explanation
太糟糕了。
您需要重构整个代码。
让我们从最糟糕的开始:
delay
是(几乎)一个繁忙的等待,自从 BASIC 是现代的以来,我还没有看到繁忙的等待。它基本上将 CPU 劫持到线程,它不仅什么也不做,其他线程(几乎)都不能使用时间片。之所以说几乎是因为调用系统时间函数会导致上下文切换,可以让其他线程运行,但还是不好
仍然很糟糕:
替换为 Thread.sleep
。更好的是,没有忙碌的等待,但你仍然持有唯一的 UI 线程。这意味着在关闭主 window.
需要发生什么:
获取外部计时器(例如javax.swing.Timer
)以触发绘图事件并执行动画的下一部分。
搜索 "Java smooth animation" 有很多关于如何执行此操作的示例,双缓冲区等等。