javaeventsjframejava-3dcanvas3d

JFrame not responding to events while handling another event


I have the following scenario: 1. I creat a JFrame jFrame in a "FrontEnd.java" class implementing ActionListener. Then I add a Canvas3D object to its ContentPane, and right afterwards, I add a JMenuBar with a few JMenus and a couple JMenuItems each. 2. Then I have a RendererClass.java class that I use to render spheres into the Canvas3D object. So from FrontEnd, clicking on one of the JMenuItems, I handle the event and from the actionPerformed(ActionEvent ae) method I call RendererClass(jFrame) and I get the jFrame object from the Renderer's side, and so the Canvas3D to paint spheres in it. So, I paint them in their initial positions. 3. Then, I update spheres' coordinates in a loop sitting in FrontEnd, which calls "updateCoordinates()" which is in the RendererClass. This is a heavy loop that may last for up to a minute. While updating sphere's coordinates, I show how are they being updated in the Canvas3D (in each iteration, coordinates vary only slightly) - this is done by the updateCoordinates() in the RendererClass.

The problem is, while in the loop, which was called from the actionPerformed(...) method, I can't interact with the jFrame, not event close it. It is actually listening, because when the loop ends, if I click while in the loop on "X" (Close Window), then the window closes. Moreover, if I tried to rotate my camera on the Canvas3D, it will not update the rotation until the loop has finished. Notice that while in the loop, I see my spheres moving. Also, the buttons stop responding and will no longer respond - the drop down JMenuItems seem to sit below the Canvas3D and become unaccessible.

Here's the code:

public class FrontEnd implements ActionListener {

    /**
     * The main Window and menus
     */
    private static JFrame jFrame = null;
    private JMenuBar jMenuBar;
    private JMenu fileMenu;
    private JMenu editMenu;
    private JMenu aboutMenu;

    private JMenuItem openAction;
    private JMenuItem exitAction;
    private JMenuItem renderAction;
    private JMenuItem aboutAction;

    /**
     * The renderer
     */
    private RendererClass renderer = null;

    /**
     * Constructor
     *
     */
    public FrontEnd() {

        jFrame = new JFrame("The Main Window");
        jFrame.getContentPane().add(new     Canvas3D(SimpleUniverse.getPreferredConfiguration()));

        jFrame.setPreferredSize(new Dimension(800, 600));
        jFrame.setResizable(false);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        /**
         * Menus
         */
        jMenuBar = new JMenuBar();
        jFrame.setJMenuBar(jMenuBar);

        //Dropdown menus
        fileMenu = new JMenu("File");
        editMenu = new JMenu("Edit");
        aboutMenu = new JMenu("About");
        jMenuBar.add(fileMenu);
        jMenuBar.add(editMenu);
        jMenuBar.add(aboutMenu);

        //Create and add simple menu item to one of the drop down menu
        openAction = new JMenuItem("Open");
        openAction.setMnemonic('O');
        exitAction = new JMenuItem("Exit");
        exitAction.setMnemonic('x');
        renderAction = new JMenuItem("Render All");
        renderAction.setMnemonic('R');
        aboutAction = new JMenuItem("About");
        aboutAction.setMnemonic('A');

        fileMenu.add(openAction);
        fileMenu.add(exitAction);
        editMenu.add(renderAction);
        aboutMenu.add(aboutAction);

        //Event Listeners
        openAction.addActionListener(this);
        exitAction.addActionListener(this);
        renderAction.addActionListener(this);
        aboutAction.addActionListener(this);

        // Configure the JFrame
        jFrame.setResizable(false);

        jFrame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent winEvent) {
                System.exit(0);
            }
        });

        jFrame.setSize(820,620);
        jFrame.setVisible(true);
        jFrame.pack();

    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == renderAction) {
            doHeavyLoop();
        }
    }

    public void doHeavyLoop() {
        renderer = new RendererClass(jFrame);
        for (int i=0; i<100000; i++) {
            try {
                    Thread.sleep(1);
                } catch (InterruptedException ie) {
                    System.out.println("Baaad MF.");
                }
                    renderer.updateCoordinates();
            }
        }
    }
}


/**
 * The RenedererClass class
 */
public static JFrame jFrame;
public SimpleUniverse universe;
public BrachGroup branchGroup;
public static PickCanvas pickCanvas;    

public RendererClass(JFrame frame) {

    jFrame = frame;
    jFrame.update(jFrame.getGraphics());

    theCanvas = (Canvas3D) jFrame.getContentPane().getComponent(0);
    theCanvas.addMouseListener(this);
    //STUFF HERE... CREATE AND ADD SPHERES TO THE BRANCHGROUP
    // Add the brachgroup to the Universe.
    universe.addBranchGraph(branchGroup);

    //The following three lines enable navigation through the scene using the mouse.
    OrbitBehavior ob = new OrbitBehavior(theCanvas);
    ob.setSchedulingBounds(new BoundingSphere(new Point3d(0.0,0.0,0.0), Double.MAX_VALUE));
    universe.getViewingPlatform().setViewPlatformBehavior(ob);

    //Now make it pickable for picking spheres
    pickCanvas = new PickCanvas(theCanvas, branchGroup);
    pickCanvas.setMode(PickCanvas.GEOMETRY);
    pickCanvas.setTolerance(0.0f);
}

public void updateCoordinates() {
    // Irrelevant... just set Translations transforms to the spheres
}

So, the question is simple... why does the JFrame window stuck and stops responding to events? And, why after the loop has finished, past events are all suddenly handled? And last but not least, how would you implement such functionality (having a JFrame created in one class, and passing it to other classing so that they can put things into a Canvas3D inside it... so that while looping, I can interact with the Canvas3D?

Thanks in advance.


Solution

  • The JFrame window stops responding to events as you are running the heavy processing task in the UI or Event Dispatch Thread.

    The solution is to run this resource heavy task in its own thread. In Swing there is a class known as a SwingWorker which will handle this type of potentially time-consuming work.

    For your application here, you could create a very simple implementation of SwingWorker at the class level like so:

    SwingWorker worker = new SwingWorker<Void, Void>() {
    
       @Override
       protected Void doInBackground() throws Exception {
          doHeavyLoop();
          return null;
       }
    };
    

    and call:

    worker.execute();
    

    in your ActionListener to execute.