我正在用Java编写Sugarscape仿真,并且需要一个可运行的GUI。 Sugarscape是一个空间景观,由(糖的)瓷砖以及移动和消耗糖的代理组成。为简单起见,我只有一种药剂,没有糖-我只想看到药剂在移动。
在过去的两周中,我已经阅读了painting in java,concurrency in java,swing中的并发性,阅读了肮脏的富客户端和无数StackOverflow线程,但是我必须在这里提问。
我需要将模型与GUI分开。这是一个问题,因为99%的教程建议在其他方法中调用重绘。我的想法是运行模拟的一个“滴答声”:所有代理移动,然后发送一个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 //这是在Agent构造函数中
2.模拟开始。实验编号:0
现在正在EDT上设置GUI? true //如上所示,这在SwingUtilities.InvokeLater部分中。但随后EDT暂停,真实模型继续:
刻度号0
调用代理动作,触发TickStart事件
TickStartEvent已创建
调用代理程序操作,立即开始循环
特工号码0现在正在移动:
现在吃糖。
现在移动。
正在睡觉。
Sugarframe已创建并添加了网格。全部在EDT上? true //再次返回。随后是绘画组件,并显示带有“代理”可见的窗口。
在EDT上调用paintComponent吗?真正
现在,我读到通过使主线程进入睡眠状态,可以给EDT时间运行重新绘制。但是,这只会发生一次。不再调用Repaint,而且我只看到过该模型的一次迭代。
我根本不了解正确使用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
}
最佳答案
句子
我需要将模型与GUI分开。这是一个问题,因为99%的教程建议在其他方法中调用重绘。
和
现在,我读到通过使主线程进入睡眠状态,可以给EDT时间运行重新绘制。
对我来说听起来不太正确,所以我会尝试澄清一些问题,也许如果您重新评估这些陈述背后的基本思想,那么您会找到丢失的信息。
首先,请始终牢记我们所讨论的调度模型。您不能说“ EDT现在为我做!”。它始终是“ EDT这是您还需要完成的另一项任务,无论您做什么都可以做。”因此,EDT有一个“任务”队列要做,并且要逐一消耗。
这些任务通常是由事件创建的:按下按钮会给EDT做一个任务,当GUI组件的状态发生变化时,可能会通知某些侦听器并使EDT中的某些工作排队。但是,您也可以直接说“ EDT稍后再执行这段代码”。这就是您使用invokeLater
所做的事情,您可以安排在免费的EDT中进行工作。即使您从EDT调用invokeLater
,任务也已安排好,但暂时不执行。invokeAndWait
也会发生同样的情况,是的,代码是按顺序执行的,就像此时已执行一样,但是它仍然是计划好的工作。因此repaint()
也不例外。 repaint()
不会重新绘制GUI,而是安排GUI的重新绘制。
但是,在可以从outside the EDT调用的意义上说,repaint()
是例外的!现在我们知道,唯一要做的就是安排特定的工作,这实际上并不与GUI混淆,因此您可以在任意位置调用它,这并不奇怪。
这意味着线
SwingUtilities.invokeLater(processEventsRunnable);
processEventsRunnable
基本上执行repaint()
的位置是没有意义的,并且整个刻度系统过于复杂和不必要。当您在GUI或GUI所馈送的数据上进行某些更改时,只需调用repaint()即可将更改反映在屏幕上。此外,如果您想做一些需要在EDT中执行的操作(例如用分数更改Label的文本),则只需将该代码放在主线程的
invokeLater
块中即可。这样就可以将队列排队并正确执行任务,您无需自己创建事件队列系统。牢记所有这些,没有任何意义:
我已经读过,通过使主线程进入睡眠状态,您可以给EDT时间运行重新绘制
在您调用repaint()之后不久,GUI便会自动更新。主要做很多事情和调用很多重绘操作不会阻止GUI的更新。但是,如果要“休眠”主机,以使更改的速度很慢,以便用户可以在屏幕上欣赏它,则应使用计时器。
因此,只要您的主要用户不访问GUI值和方法,就可以在无论是否定期更改数据时随时调用重绘。
编辑:另外,您有一个主线程在做事务,这听起来有些奇怪。正如您在并发性章节中所读到的那样,通常只需在EDT中创建GUI,然后在按下按钮等操作时,应用程序大多是事件驱动的。如果您需要定期进行更改,请使用计时器。您可以使用辅助线程来完成特定于非GUI的繁重工作,例如读取文件。但是您通常不会在设计中永久启用辅助线程。
以下是一个非常简单的程序,它会定期移动一个正方形。我只是使用计时器来更改数据并调用repaint()。请注意,由于要检查面板宽度,因此我正在使用SwingTimer(在EDT中执行)。否则,我可以在任何线程中运行计时器的代码。
在您的情况下,您可能独立于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);
}
}
}
关于java - 经常更新的真实模型中了解EDT,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23786574/