StarlingInCahoots
StarlingInCahoots

Reputation: 5

Leave Event Dispatch Thread entry ONLY on key press (Java)

I understand that it is important to use the Event Dispatch Thread for any changes to the interface in Java. However, I have no idea how I can manipulate these events to stop/continue/start. I want to refrain from moving on to the next line of main() (after the ones which put the Runnable in the EventQueue) until a certain key is pressed.

I put together an example for clarity. What I'd like to do here is spawn the JFrame, allow the user to move the box around with the arrow keys and then press Enter to cease the box-shifting operations, and ONLY then make the calculation at the end of main() and cause the answer to appear. I should be able to get 400, 500, 600, etc. As it is, the calculation is made immediately after the JFrame appears, so the answer is always 300.

I carved out a spot for whatever action should be bound to Enter; it's underneath the declarations for the actions bound to the arrow keys.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class EndTheShifter extends JFrame
{

    private Color ourRectColor = new Color(28,222,144);
    private int ourRectWidth = 50;
    private int ourRectHeight = 50;

    protected static Point ourRecLocation = new Point(100,100);


    // Rectangle object can paint itself

    public class Rectangle
    {
        protected void paint(Graphics2D g2d)
        {
            g2d.setColor(ourRectColor);
            g2d.fillRect(ourRecLocation.x, ourRecLocation.y, ourRectWidth, ourRectHeight);
        }

    } // Rectangle class


    // OurRectangle can create a Rectangle and call paint() on it

    public class OurRectangle extends JPanel
    {

        private Rectangle capableRectangle;

        public OurRectangle()
        {
            capableRectangle = new Rectangle();
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D)g.create();

            capableRectangle.paint(g2d);

            g2d.dispose();
        }

    } // OurRectangle class


    KeyStroke pressRight = KeyStroke.getKeyStroke("RIGHT");
    KeyStroke pressLeft = KeyStroke.getKeyStroke("LEFT");
    KeyStroke pressUp = KeyStroke.getKeyStroke("UP");
    KeyStroke pressDown = KeyStroke.getKeyStroke("DOWN");
    KeyStroke pressEnter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0);

    OurRectangle recToWorkWith = new OurRectangle();


    // Create InputMap and ActionMap

    InputMap inputMap = recToWorkWith.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
    ActionMap actionMap = recToWorkWith.getActionMap();

    // Mapping Shortcut

    protected void setTheAction(KeyStroke a, String b, Action c)
    {
        inputMap.put(a,b);
        actionMap.put(b,c);
    }


    // Constructor!!!

    public EndTheShifter()
    {

        add(recToWorkWith);

        Action rightAction = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                if(ourRecLocation.x != 600)
                    ourRecLocation.x += 50;
                else
                    ourRecLocation.x = 100;

                recToWorkWith.repaint();
            }
        };

        Action leftAction = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                if(ourRecLocation.x != 100)
                    ourRecLocation.x -= 50;
                else
                    ourRecLocation.x = 600;

                recToWorkWith.repaint();
            }
        };

        Action downAction = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                if(ourRecLocation.y != 600)
                    ourRecLocation.y += 50;
                else
                    ourRecLocation.y = 100;

                recToWorkWith.repaint();
            }
        };

        Action upAction = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                if(ourRecLocation.y != 100)
                    ourRecLocation.y -= 50;
                else
                    ourRecLocation.y = 600;

                recToWorkWith.repaint();
            }
        };

/*
        Action enterAction = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {

            }
        }

        setTheAction(pressEnter,"enterAction",enterAction);
*/

        setTheAction(pressRight,"rightAction",rightAction);
        setTheAction(pressLeft,"leftAction",leftAction);
        setTheAction(pressDown,"downAction",downAction);
        setTheAction(pressUp,"upAction",upAction);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(800,800);
        setVisible(true);
    }


    // Main kicks things off by putting all of the above
    // in the Event Dispatch thread

    // On an enter press, I want the last line of main() to run

    public static void main(String[] argv)
    {
        EventQueue.invokeLater(

        new Runnable()
        {
            @Override
            public void run()
            {
                new EndTheShifter();
            }
        });

        // What I want to trigger only on Enter

        System.out.println(ourRecLocation.x + 2*ourRecLocation.y);
    }

} // EndTheShifter, our outermost class

Upvotes: 0

Views: 100

Answers (2)

Charlie Armstrong
Charlie Armstrong

Reputation: 2342

I would like to support what camickr said; there is likely a better way to achieve what you are trying to do. That said, if you really want to make your main method wait until the enter key is pressed, here's how:

First, at the top of your file, define an object to use as a synchronization lock like so:

public static final Object LOCK = new Object();

Then, in your main method, before your println statement, put the following code:

synchronized (LOCK) {
    LOCK.wait();
}

What this does is it waits until the LOCK object's monitor lock is not being used by any thread (very simplified explanation, read more here), and then it makes the current thread (in this case, the thread that started your main method) wait indefinitely.

Next, add a throws declaration to the method header on your main method:

public static void main(String[] argv) throws InterruptedException

This tells the compiler that your code could throw an InterruptedException, which would happen if your thread was interrupted while it was waiting.

Finally, anywhere in your EndTheShifter constructor, put the following code:

synchronized (LOCK) {
    LOCK.notify();
}

This again waits until the LOCK object's monitor lock becomes available, and it then "notifies" all threads waiting on the LOCK object that they may continue. In this case, it will make our main thread continue and execute the println.

Upvotes: 0

camickr
camickr

Reputation: 324157

and ONLY then make the calculation at the end of main()

That is not the way a GUI works.

The main() method is only used to display the frame.

Once the frame is visible the EDT is started and the frame sits there waiting for user events to be generated.

Your application code then responds to these user events.

I understand that it is important to use the Event Dispatch Thread for any changes to the interface in Java.

All code invoked in a listener does execute on the EDT. So the code in your Action does execute on the EDT. You don't need to do anything special.

What I want to trigger only on Enter

Then that logic should be contained in the Enter Action.

Upvotes: 3

Related Questions