【发布时间】:2014-05-21 14:44:12
【问题描述】:
我正在用 Java 编写 Sugarscape 模拟,需要一个可用的 GUI。 Sugarscape 是一种空间景观,由(糖的)瓷砖和移动和消耗糖的代理组成。为简单起见,我只有一个代理,没有糖——我只想看到代理移动。
在过去的两周里,我读过painting in java、concurrency in java、swing 并发,我读过肮脏的富客户端和无数的 StackOverflow 线程,但我必须在这里提出一个问题。
我需要将我的模型与 GUI 分开。这带来了一个问题,因为 99% 的教程建议在其他方法中调用重绘。我的想法是运行模拟的一个“滴答”:所有代理移动,然后发送一个事件(我的 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.代理创建。起始位置:X= 19 Y= 46 // 这是在代理构造函数中
2.模拟开始。实验编号:0
现在在 EDT 上设置 GUI? true // 如您在上面看到的,这是在 SwingUtilities.InvokeLater 部分内。但随后 EDT 暂停,真实模型继续:
刻度数 0
调用代理操作,触发 TickStart 事件
已创建 TickStartEvent
调用代理操作,for-loop 现在开始
0 号特工现在搬家:
现在吃糖。
现在移动。
现在睡觉。
Sugarframe 已创建并添加了网格。全部在美国东部时间? true // 然后它又回来了。绘制组件随后出现,代理可见的窗口出现。
在 EDT 上调用了paintComponent?真的
现在,我已经了解到,通过让主线程进入睡眠状态,您可以给 EDT 时间来运行重绘。然而,这只会发生一次。 Repaint 再也不会被调用,而且我只看到过模型的一次迭代。
我只是不明白我缺少哪些信息才能正确使用 EDT。 Swingworker 和 Swingtimer 经常被推荐,但对于每一个建议,都有一个概念,即像我这样的模型不需要它们。要么根本不调用paintComponent,要么排队到最后(即使我使用thread.sleep,仍然不会重新绘制)。
如果有任何帮助,我将不胜感激。为长篇道歉。
//编辑:根据要求添加更多代码。 整个main方法:
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
}
【问题讨论】:
-
模拟的其余部分实际发生在哪里?最好提供更多关于模拟其余部分的代码,因为仅从跟踪中准确了解正在发生的事情是很复杂的。
-
嗯,我有大约 17 节课。一个类,模拟,需要所有对象并包括调用模型动作的方法。 SimulationController 是建立模型的主要方法。 SugarFrame 是 JFrame,Grid 是 JPanel。我尝试添加一段相关的代码,因为我无法在此处发布所有代码。
-
“现在,我已经读到,通过让主线程进入睡眠状态,你可以给 EDT 时间来运行重绘。”这是没有意义的。 EDT 处理代码块,主要是事件。 EDT “有时间”来处理您安排的事件,只要它不忙于较旧的事件。显然,处理每个事件非常快,您唯一需要担心的是您没有在其中一个事件中阻塞 EDT。例如,在 EDT 中调用
sleep是一个主要问题,因为这会停止所有事件处理并且 GUI 冻结。主线程的状态无关紧要。 -
您能否详细说明“我需要将我的模型与 GUI 分开”。如果程序背后的逻辑和数据很好并且值得推荐,那么“模型”是什么意思。教程通常在其他方法中调用 repaint() 只是因为它们在修改 GUI 后调用它,无论在哪里,都没有等待的意义。另请注意,
repaint()实际上并不重绘 GUI,而是在 EDT 中安排重绘任务。只要它是免费的,它就会被执行。这将很快,但它与调用是异步的。 -
是的,“模型”是指糖景的逻辑和数据。 IE。我为 GUI 提供了单独的类,这些类将依赖于从主模型传递的事件,而不是使用糖或移动包含与 GUI 相关的代码的方法。是的,正是日程安排让我头疼。我不知道如何适应异步线程。
标签: java swing concurrency event-dispatch-thread