Search code examples
javaswingmodal-dialogjframedispose

Java not disposing dialog when frame is not visible


I am having a problem with hidden components not being disposed properly when working with multiple frames.

In short, I cannot dispose a modal dialog whose parent is a hidden frame.

For example:

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

public class MultipleFrameTest {
    public static void main(String[] args) {
        TestFrame   test   = new TestFrame();
        FrameTester tester = new FrameTester(test);

        tester.setVisible(true);
    }

    private static class TestFrame extends JFrame {
        JDialog dialog;
        java.util.Timer timer;

        public TestFrame() {
            super("Test Frame");

            this.dialog = null;
            this.timer  = new java.util.Timer("Frame Timer");

            fillFrame();
            pack();
        }

        private void fillFrame() {
            JButton dialogButton = new JButton("Launch Model Dialog");
            dialogButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    JOptionPane pane = new JOptionPane("Wait for 2 seconds",
                        JOptionPane.QUESTION_MESSAGE,
                        JOptionPane.OK_CANCEL_OPTION);
                    dialog = pane.createDialog(TestFrame.this, "Question");

                    timer.schedule(new TimerTask() {
                        public void run() {
                            SwingUtilities.invokeLater(new Runnable() {
                                public void run() {
                                    TestFrame.this.setVisible(false);

                                    if (dialog != null) {
                                        dialog.setVisible(false);
                                        dialog.dispose();
                                        dialog = null;
                                    }
                                }
                            });
                        }
                    }, 2 * 1000);

                    dialog.setVisible(true);
                }
            });

            JPanel panel = new JPanel();
            panel.add(dialogButton);

            add(panel);
        }
    }    

    private static class FrameTester extends JFrame {
        JFrame frame;

        public FrameTester(JFrame frame) {
            super("Frame Tester");

            this.frame = frame;

            fillFrame();
            pack();
        }

        private void fillFrame() {
            JButton toggleButton = new JButton("Toggle Frame Visibility");
            toggleButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    frame.setVisible(!frame.isVisible());
                }
            });

            JPanel panel = new JPanel();
            panel.add(toggleButton);

            add(panel);
        }
    }
}

To run this example:

  1. Click the "Toggle Frame Visibility" button. This will show the TestFrame.
  2. Click the "Launch Modal Dialog" button. This will pop up a JOptionPane.
  3. Wait 2 seconds for a TimerTask to hide the TestFrame and dispose() the JOptionPane.
  4. Click the "Toggle Frame Visibility Button". The TestFrame will become visible and the JOptionPane will see be attached.

I know that I can fix this by disposing the JOptionPane before hiding the TestFrame:

- TestFrame.this.setVisible(false);

  if (dialog != null) {
     dialog.setVisible(false);
     dialog.dispose();
     dialog = null;
  }

+ TestFrame.this.setVisible(false);

Does anybody know why this is happening? I would expect the modal dialog to be gone even if it is hidden while it is being disposed.


Solution

  • Although you should really consider trashgod's comment on The Use of Multiple JFrames, Good/Bad Practice?, here is the explanation on why you see this behaviour.

    This happens because you hide the owning window before the dialog. When doing such thing, the owned dialog is flagged as showWithParent and the next call to setVisible(true) on the frame will automatically trigger the display of the owned dialog, because of that flag. The only way to avoid that, as you stated in your question, is to hide the owned dialog first, then hide the owning window.

    Here is the extract of the Window.hide() method:

        synchronized(ownedWindowList) {
            for (int i = 0; i < ownedWindowList.size(); i++) {
                Window child = ownedWindowList.elementAt(i).get();
                if ((child != null) && child.visible) {
                    child.hide();
                    child.showWithParent = true; // See here the flag set to true
                }
            }
        }
    

    and here the corresponding extract of the Window.show() method:

           for (int i = 0; i < ownedWindowList.size(); i++) {
                Window child = ownedWindowList.elementAt(i).get();
                if ((child != null) && child.showWithParent) { // Here theh flag is checked
                    child.show();
                    child.showWithParent = false; // flag is then reset
                }       // endif
            }
    

    Btw, instead of using this complex timer/TimerTask/invokeLater structure, you could just use javax.swing.Timer (with setRepeats(false)). The Swing timer always runs on the Event Dispatching Thread.