我正在用Java编写Sugarscape模拟,需要一个可用的GUI。 Sugarscape是一个空间景观,由瓷砖(糖),药剂移动和消费糖组成。为简单起见,我只有一个代理人而且没有糖 - 我只是想让代理人感动。
在过去的两周里,我已经阅读了painting in java,concurrency in java,并发摆动,我阅读了肮脏的富客户端和无数的StackOverflow线程,但我必须在这里提出问题。
我需要将我的模型与GUI分开。这提出了一个问题,因为99%的教程建议在其他方法中调用重绘。我的想法是运行一个" tick"模拟:所有代理移动,然后发送一个Event(我的GUI类扩展Observer),然后触发一个repaint();请求并更新GUI。然而问题(误解)在于SwingUtilities.InvokeLater方法。我的代码是:
public void setupGUI()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run() {
System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
SugarFrame frame = new SugarFrame(simulation.getWorld());
frame.setVisible(true);
}
});
}
为了理解发生了什么,我到处都插入了println。事件的顺序令我感到困惑:
控制台输出:
1.Agent创建。起始位置:X = 19 Y = 46 //这是在代理构造函数
中2.模拟开始。实验编号:0
现在正在EDT上设置GUI吗? true //如上所示,这是在SwingUtilities.InvokeLater部分中。但随后EDT暂停,真正的模型继续:
勾号0
调用代理操作,触发TickStart事件
TickStartEvent已创建
调用代理操作,从现在开始for循环
现在正在移动代理商0:
现在正在消费糖。
现在搬家。
现在睡觉。
已创建Sugarframe并添加了Grid。全部在EDT?是的//它又回来了。随后是paint组件,并显示代理可见的窗口。
在EDT上调用paintComponent?真
现在,我已经读过,通过让主线程处于休眠状态,你可以给EDT时间来运行重绘。但是,这只发生过一次。永远不再调用重绘,我只看到模型的一次迭代。
我根本不明白我错过了哪些信息可以正确使用EDT。定期建议使用Swingworker和Swingtimer,但是对于每个建议都有一个概念,即我的模型不需要它们。 paintComponent根本没有被调用,或排队到最后(然后仍然没有重新绘制,即使我使用thread.sleep)。
我很感激任何帮助。为长篇大论道歉。
//编辑:根据请求提供更多代码。 整个主要方法:
public class SimulationController {
static Simulation simulation;
public static final int NUM_EXPERIMENTS = 1;
public SimulationController()
{
Random prng = new Random();
SimulationController.simulation = new Simulation(prng);
}
public void run() {
setupGUI();
for(int i=0; i<NUM_EXPERIMENTS; i++) {
System.out.println("Simulation start. Experiment number: " + i);
simulation.getWorld().addObserver(simulation);
simulation.addObserver(simulation.getWorld());
simulation.run();
}
}
public void setupGUI()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run() {
System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
SugarFrame frame = new SugarFrame(simulation.getWorld());
frame.setVisible(true);
}
});
}
public static void main(String[] args) {
SimulationController controller = new SimulationController();
controller.run();
}
}
我的JPanel类中的绘制覆盖:
@Override
public void paintComponent(Graphics g) {
System.out.println(">>>>>>>>paintComponent called, on EDT? " + SwingUtilities.isEventDispatchThread()+"<<<<<<<<<<");
super.paintComponent(g);
//g.clearRect(0, 0, getWidth(), getHeight());
rectWidth = getWidth() / world.getSizeX();
rectHeight = getHeight() / world.getSizeY();
for (int i = 0; i < world.getSizeX(); i++)
{
for (int j = 0; j < world.getSizeY(); j++)
{
// Upper left corner of this terrain rect
x = i * rectWidth;
y = j * rectHeight;
Tile tile = world.getTile(new Position(i, j));
if (tile.hasAgent())
{
g.setColor(Color.red);
} else
{
g.setColor(Color.black);
}
g.fillRect(x, y, rectWidth, rectHeight);
}
}
}
再次使用JPanel类,更新方法:
public void update(Observable o, Object arg)
{
if (arg instanceof TickEnd)
{
TickEvent tickEndevent = new TickEvent();
this.addTickEvent(tickEndevent);
}
}
}
private final BlockingQueue<TickEvent> TICK_EVENTS = new LinkedBlockingQueue<TickEvent>();
/**Runnable object that updates the GUI (I think)**/
private final Runnable processEventsRunnable = new Runnable()
{
public void run()
{
TickEvent event = new TickEvent();
while ((event = TICK_EVENTS.poll()) != null)
{
System.out.println("This is within processEventsRunnable, inside the While loop. Repaint is called now.");
repaint();
}
}
};
/**Add Event to the processing-Events-queue**/
public void addTickEvent(TickEvent event)
{
//System.out.println("This is in the Add TickEvent method, but before the adding. "+TICK_EVENTS.toString());
TICK_EVENTS.add(event);
System.out.println("TickEvent has been added! "+TICK_EVENTS.toString() + "On EDT?" + SwingUtilities.isEventDispatchThread());
if (TICK_EVENTS.size() >= 1)
{
SwingUtilities.invokeLater(processEventsRunnable);
}
}
最后但并非最不重要的是,JFrame构造函数:
/** Sugarframe Constructor**/
public SugarFrame(World world)
{
super("Sugarscape"); // creates frame, the constructor uses a string argument for the frame title
grid = new Grid(world); // variable is declared in the class
add(grid);
setDefaultCloseOperation(EXIT_ON_CLOSE); // specifies what happens when user closes the frame. exit_on_close means the program will stop
this.setContentPane(grid);
this.getContentPane().setPreferredSize(new Dimension(500, 500));
this.pack(); // resizes frame to its content sizes (rather than fixed height/width)
System.out.println("The Sugarframe has been created and Grid added. All on EDT? "+ SwingUtilities.isEventDispatchThread());
this.setVisible(true); // makes the Frame appear on screen
}
答案 0 :(得分:1)
句子,
我需要将我的模型与GUI分开。这提出了一个问题,因为99%的教程建议在其他方法中调用重绘。
和
对我来说听起来不对,所以我会尝试清理一些事情,也许如果你重新评估这些陈述背后的基本想法,你可以找到你所掌握的信息。丢失。现在,我已经读过,通过让主线程处于休眠状态,你可以给EDT时间来运行重绘。
首先,请始终牢记我们所讨论的这种调度模型。你不能说&#34; EDT现在为我做这个!&#34;。它总是&#34; EDT这是你需要做的另外一项任务,当你完成了你正在做的任何事情时做到这一点&#34;。所以EDT有一个&#34;任务队列#34;要做,并逐一消耗它。
这些任务通常由事件创建:按下按钮为EDT提供了一项任务,当GUI组件的状态发生变化时,某些听众可能会被通知并在EDT中排队一些工作。但是,你也可以直接说&#34; EDT执行这段代码,稍后&#34;。这就是你对invokeLater
所做的事情,你可以安排在EDT任何时候做的工作。即使您从EDT呼叫invokeLater
,任务也会被安排,而不会在此时执行。
同样的情况发生在invokeAndWait
是的,代码按顺序执行,就好像它现在被执行一样,但它仍然是一个预定的工作。所以repaint()
也不例外。 repaint()
不会重新绘制GUI,而是安排重新绘制GUI。
然而,repaint()
在可以从outside the EDT调用的意义上是例外的!这并不奇怪,因为我们知道唯一能做的就是安排某项工作,它实际上并没有弄乱GUI,所以你可以随意调用它。
这意味着该行
SwingUtilities.invokeLater(processEventsRunnable);
其中processEventsRunnable
基本上执行repaint()
是没有意义的,整个滴答系统过于复杂和不必要。您只需在GUI上更改某些内容或GUI上提供的数据时调用repaint(),以便更改反映在屏幕上。
此外,如果你想做一些需要在EDT中执行的事情(比如用分数更改Label的文本),你可以将代码放在主线程的invokeLater
块中。这将正确排队并执行任务,您不需要自己创建事件队列系统。
记住以下所有这一点毫无意义:
我已经看过,通过让主线程进入休眠状态,你可以给EDT时间来运行重绘
在调用repaint()后不久,GUI将自动更新。主要做很多事情并调用大量重绘不会阻止GUI更新。但是,如果你想睡觉&#34;主要因此变化的速度很慢,所以用户可以在屏幕上欣赏它,你应该使用计时器。
因此,只要您的主要访问GUI值和方法,只要您定期更改数据,就可以随时调用重绘。
编辑:你也有一个主线程做事听起来有点奇怪。正如您在并发章节中所读到的那样,通常您只是在EDT中创建GUI,然后当按下按钮等时,应用程序主要是事件驱动的。如果需要定期更改,请使用计时器。您可以使用辅助线程来执行特定的非GUI相关繁重工作,例如读取文件。但是,作为设计的一部分,您通常不会永久激活辅助线程。
以下是一个非常简单的程序,可以定期移动方块。我只是使用计时器来更改数据并调用repaint()。请注意,我使用SwingTimer(它在EDT中执行),因为我想检查面板宽度。否则我可以在任何线程中运行计时器的代码。
在你的情况下,你可能有你的&#34;地图&#34;独立于GUI存储,因此您只需要检查该数据,以便在需要时(键盘按下,定期...)正确移动代理的坐标。
看起来像这样:
完整代码:
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MovingSquareTest
{
int x, y, size, step;
MyPanel panel;
Timer timer;
public static final void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
MovingSquareTest app = new MovingSquareTest();
app.createAndShowGUI();
app.timer.start();
}
});
}
public MovingSquareTest()
{
x = 0;
y = 150;
size = 50;
step = 50;
timer = new Timer(500, new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
x += step;
if (x < 0) x = 0;
if (x + size > panel.getWidth()) x = panel.getWidth() - size;
if (x == 0 || x + size == panel.getWidth()) step *= -1;
panel.repaint();
}
});
}
public void createAndShowGUI()
{
JFrame frame = new JFrame("Dance, my square!");
panel = new MyPanel();
frame.add(panel);
frame.setSize(600, 400);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private class MyPanel extends JPanel
{
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawRect(x, y, size, size);
}
}
}