Custom dialog using JOptionPane API won't disp

2019-05-16 09:39发布

问题:

I've been just playing with JOptionPane API to show a custom dialog and I've found a strange situation: when I choose either OK or Cancel option or press Esc key this dialog won't dispose as expected.

The thing is, instead of using this single line to display a modal dialog:

JOptionPane.showConfirmDialog( null
                            , "The quick brown fox jumps over the lazy dog."
                            , "New Dialog"
                            , JOptionPane.OK_CANCEL_OPTION
                            , JOptionPane.PLAIN_MESSAGE);

I wanted to use the API, setting all the parameters one by one and displaying a dialog as shown in the docs (see Direct Use section):

 JOptionPane pane = new JOptionPane(arguments);
 pane.set.Xxxx(...); // Configure
 JDialog dialog = pane.createDialog(parentComponent, title);
 dialog.show();

However, when I close the dialog my application keeps running, even if I set the default close operation to DISPOSE_ON_CLOSE, and it makes me suspect the dialog is not properly disposed.

Below is a MCVE to play with:

import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

public class Demo {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JOptionPane optionPane = new JOptionPane();
                optionPane.setMessage("The quick brown fox jumps over the lazy dog.");
                optionPane.setOptionType(JOptionPane.OK_CANCEL_OPTION);
                optionPane.setMessageType(JOptionPane.PLAIN_MESSAGE);

                JDialog dialog = optionPane.createDialog("New Dialog");
                dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
                dialog.setVisible(true);
            }
        });
    }
}

回答1:

Well, inspecting JOptionPane#createDialog(String title) source code, it turns out the dialog is not closed nor disposed but hidden instead, and it's done on a PropertyChangeEvent when the option pane's value is set (to CANCEL_OPTION or NO_OPTION or CLOSED_OPTION I guess):

public class JOptionPane extends JComponent implements Accessible {

    ...

     // The following method is called within 'createDialog(...)'
     // in order to initialize the JDialog that will be retrieved.

    private void initDialog(final JDialog dialog, int style, Component parentComponent) {
        ...
        final PropertyChangeListener listener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {
                // Let the defaultCloseOperation handle the closing
                // if the user closed the window without selecting a button
                // (newValue = null in that case).  Otherwise, close the dialog.
                if (dialog.isVisible() && event.getSource() == JOptionPane.this &&
                        (event.getPropertyName().equals(VALUE_PROPERTY)) &&
                        event.getNewValue() != null &&
                        event.getNewValue() != JOptionPane.UNINITIALIZED_VALUE) {
                    dialog.setVisible(false);
                }
            }
        };

        WindowAdapter adapter = new WindowAdapter() {
            private boolean gotFocus = false;
            public void windowClosing(WindowEvent we) {
                setValue(null);
            }

            public void windowClosed(WindowEvent e) {
                removePropertyChangeListener(listener);
                dialog.getContentPane().removeAll();
            }

            public void windowGainedFocus(WindowEvent we) {
                // Once window gets focus, set initial focus
                if (!gotFocus) {
                    selectInitialValue();
                    gotFocus = true;
                }
            }
        };
        dialog.addWindowListener(adapter);
        dialog.addWindowFocusListener(adapter);
        ...
    }
}

Despite the comment that states "Otherwise, close the dialog", no WindowEvent closing the dialog is even fired. Consequently the dialog won't be disposed properly unless we explicitely do so:

    JDialog dialog = optionPane.createDialog("New Dialog");
    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
    dialog.setVisible(true);
    dialog.dispatchEvent(new WindowEvent(dialog, WindowEvent.WINDOW_CLOSING));

Please note on a WINDOW_CLOSING event the WindowListener attached to the dialog within initDialog(...) method will just set the option pane's value to null but still won't dispose the dialog. That's why we still need to set the default close operation to DISPOSE_ON_CLOSE.