Search code examples
javaswingjframejoptionpane

How to close JOptionPane directly by closing JFrame?


I am new to learning javax.swing and have a doubt.

I have a JFrame whose defaultCloseOperation is set to EXIT_ON_CLOSE. I have another component - a JOptionPane - over the JFrame. What I want to happen is that even when the focus is on the JOptionPane message dialog, I want the program to terminate when I click the x button on the JFrame window.

So, precisely, I'm looking to bring the JFrame in focus without dismissing the JOptionPane message dialog is over it so that I can close the JFrame window and, consequently, make the program terminate.

Here is my code:

import javax.swing.*;

public class JJSS {
    public JFrame jf;

    public JJSS(){
        jf = new JFrame();

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

    public void runMethod(){
        String str = JOptionPane.showInputDialog(jf, "Enter something...");
        str = String.valueOf(new StringBuffer(str).reverse());
        JOptionPane.showMessageDialog(jf, "Reversed: "+str, "Output", JOptionPane.PLAIN_MESSAGE);
    }

    public static void main(String[] args){
        new JJSS().runMethod();
        System.exit(0);
    }

}

With the current code, nothing happens when I click on the close button (x) of the JFrame window.

How do I bring the focus on the JFrame window, while the JOptionPane dialog is still on it, and terminate the program by closing the JFrame window?


Solution

  • Dialog modality is the key

    You can't do this with a normal JOptionPane or with any modal dialog as the modality prevents the user from interacting with other components of the GUI while the dialog is displayed. You can only get this to work if you create a non-modal dialog, meaning that the JOptionPane must be created not with the JOptionPane static factory methods, but rather in a non-traditional way, using a JOptionPane constructor -- check the JOptionPane API for how to do this.

    For example:

    import java.awt.Dialog.ModalityType;
    import java.awt.Dimension;
    import java.awt.event.ActionEvent;
    
    import javax.swing.*;
    
    public class NonModalJOptionPane {
    
        private static void createAndShowGui() {
            JPanel panel = new JPanel();
            panel.setPreferredSize(new Dimension(400, 300));
    
            final JFrame frame = new JFrame("NonModalJOptionPane");
    
            panel.add(new JButton(new AbstractAction("Push Me") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    JOptionPane optionPane = new JOptionPane("My Message", JOptionPane.PLAIN_MESSAGE);
                    JDialog dialog = optionPane.createDialog(frame, "My Option");
                    dialog.setModalityType(ModalityType.MODELESS); // **** key ***
                    dialog.setVisible(true);
                }
            }));
    
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(panel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> createAndShowGui());
        }
    }
    

    The key to this code is here:

    // create the JOptionPane using one of its constructors
    JOptionPane optionPane = new JOptionPane("My Message", JOptionPane.PLAIN_MESSAGE);
    
    // create a JDialog from it, tying it to the parent JFrame, here called "frame"
    JDialog dialog = optionPane.createDialog(frame, "My Option");
    
    // setting the modality type so that it is **not** modal
    dialog.setModalityType(ModalityType.MODELESS); // **** key ***
    
    // and then displaying it
    dialog.setVisible(true);
    

    Where I create a JOptionPane via its constructor, not via a static method, I create a JDialog and set it to be MODELESS, and then display it.


    Another viable option is to create your own JDialog, making sure that you set it to being non-modal as well.

    For example, you could add the following code to the code above:

    panel.add(new JButton(new AbstractAction("Push Me 2 -- Using Dialog") {
        @Override
        public void actionPerformed(ActionEvent e) {
            // button that when pressed, closes the JDialog that holds it
            // similar to a JOptionPane's OK button
            JButton disposeWinBtn = new JButton(new DisposeWindowAction("OK", KeyEvent.VK_O));
    
            // create a bunch of JPanels, add components to them, ...
            JPanel bottomPanel = new JPanel();
            bottomPanel.add(disposeWinBtn);
    
            JLabel msgLabel = new JLabel("My Message");
            JPanel msgPanel = new JPanel();
            msgPanel.add(msgLabel);
    
            JPanel panel = new JPanel(new BorderLayout());
            panel.add(msgPanel, BorderLayout.CENTER);
            panel.add(bottomPanel, BorderLayout.PAGE_END);
    
            // create a JDialog whose parent component is the main JFrame
            // and make sure that it is *****non-modal ***** <===== this is KEY *****
            JDialog dialog = new JDialog(frame, "Dialog", ModalityType.MODELESS);
            dialog.add(panel);  // add the JPanel, panel, created above, with components
            dialog.pack();  // have layout managers do their thing
            dialog.setLocationRelativeTo(frame); // center it over the main JFrame
            dialog.setVisible(true);  // and display it
        }
    }));
    

    just under where the first button is added. You'll also need the DisposeWindowAction class, that allows the button to close and dispose of the window that is displaying it (here a JDialog window):

    import java.awt.Component;
    import java.awt.Window;
    import java.awt.event.ActionEvent;
    import javax.swing.AbstractAction;
    import javax.swing.SwingUtilities;
    
    @SuppressWarnings("serial")
    public class DisposeWindowAction extends AbstractAction {
        public DisposeWindowAction(String name, int mnemonic) {
            super(name);
            putValue(MNEMONIC_KEY, mnemonic);
        }
    
        @Override
        public void actionPerformed(ActionEvent e) {
            Component component = (Component) e.getSource();
            if (component == null) {
                return;
            }
            Window win = SwingUtilities.getWindowAncestor(component);
            if (win == null) {
                return;
            }
            win.dispose();
        }
    }