João Cartucho
João Cartucho

Reputation: 3916

Java wait code until JFrame keyPressed

I'm using a JFrame and I wanted to display an image and pause the code until the user presses ANY key. After that key being pressed the image would close and the code would continue running.

What I did:

This is working, but I understand that it is a bit resourceful.

Is there any other way of making the wait loop? Is there any listener of the listener?

2nd try, using CountDownLatch:

Upvotes: 0

Views: 1057

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347314

So, you want to display an image and have the execution stop until the window is closed. This just screams modal dialog to me. A modal dialog will stop the code execution from where it is made visible, it will do it in such away so as not to block the Event Dispatching Thread and make your entire problem come to a screaming halt and hang the program. See How to use dialogs for more details...

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Image;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    BufferedImage img = ImageIO.read(...);
                    ImageShower.show(null, img);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public static class ImageShower extends JPanel {

        private JLabel label = new JLabel();

        public ImageShower() {
            setLayout(new BorderLayout());
            add(label);

            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close");
            am.put("close", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Window window = SwingUtilities.windowForComponent(ImageShower.this);
                    if (window != null) {
                        window.dispose();
                    }
                }
            });
        }

        public void setImage(Image img) {
            label.setIcon(new ImageIcon(img));
        }

        public static void show(Component owner, Image img) {
            Window parent = null;
            if (owner != null) {
                parent = SwingUtilities.windowForComponent(owner);
            }

            JButton close = new JButton("Close");
            close.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    JButton btn = (JButton) e.getSource();
                    Window window = SwingUtilities.windowForComponent(btn);
                    if (window != null) {
                        window.dispose();
                    }
                }
            });

            JDialog dialog = new JDialog(parent, Dialog.ModalityType.APPLICATION_MODAL);
            ImageShower shower = new ImageShower();
            shower.setImage(img);
            dialog.add(shower);
            dialog.add(close, BorderLayout.SOUTH);
            dialog.getRootPane().setDefaultButton(close);
            dialog.pack();
            dialog.setLocationRelativeTo(owner);
            dialog.setVisible(true);
        }

    }

}

"But wait, may images are large and take time to load and I don't want to freeze the UI while the load"...

Okay, for that, I'd look towards using a SwingWorker, which can load the image in the background but which provides simple methods for ensuring the the image is displayed within the context of the EDT properly...

public class ImageLoadAndShow extends SwingWorker<Void, Image> {

    @Override
    protected Void doInBackground() throws Exception {
        BufferedImage img = ImageIO.read(...);
        publish(img);
        return null;
    }

    @Override
    protected void process(List<Image> chunks) {
        Image img = chunks.get(chunks.size() - 1);
        ImageShower.show(null, img);
    }

}

Not, if the image fails to load, you won't know about it, as the doInBackground method will pass the Exception out of the method. You'd need to use a combination of a PropertyChangeListener and the SwingWorkers get method to trap it, just remember, get is blocking, so calling it inside the context of the EDT will block until the worker completes

"But I need to carry out other operations when the dialog is closed"

There are a few ways you might be able to achieve this, depending on what it is you want to do, for this example, I've stuck with the SwingWorker, because it was easy to copy and paste the basic structure, but you could use a Runnable wrapped in a Thread

public class ImageLoadShowAndWait extends SwingWorker<Void, Void> {

    @Override
    protected Void doInBackground() throws Exception {
        BufferedImage img = ImageIO.read(...);
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                ImageShower.show(null, img);
            }
        });
        return null;
    }

}

Now, if none of that does what you want...then I'd like to know what it is you're actually doing :P, have a look at Foxtrot which provides an API which allows you to execute code asynchronisly within the EDT without blocking it (entirly), but which will stop the code execution at the point it's called until it completes

The thing is that I wanted it to close the JFrame when ANY key is pressed

KeyListener is going to give you issues, maybe not today, maybe not tomorrow, but it will blow up in your face eventually. The example I've provide binds the Escape key to dispose of the window. It also makes the "Close" button the default button, which provides Space and/or Enter keys as well and a nice visual queue to the user.

If you want to use KeyListener, that's up to you, but your core problem doesn't seem to revolve around it, but the ability to display a window and pause the code execution till it's closed

Upvotes: 2

Related Questions