我正在用Java编写Sugarscape仿真,并且需要一个可运行的GUI。 Sugarscape是一个空间景观,由(糖的)瓷砖以及移动和消耗糖的代理组成。为简单起见,我只有一种药剂,没有糖-我只想看到药剂在移动。

在过去的两周中,我已经阅读了painting in javaconcurrency 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/

10-12 04:57