6
// This is supposed to show a modal dialog and then hide it again. In practice,
// this works about 75% of the time, and the other 25% of the time, the dialog
// stays visible.
// This is on Ubuntu 10.10, running:
// OpenJDK Runtime Environment (IcedTea6 1.9) (6b20-1.9-0ubuntu1)

// This always prints
// setVisible(true) about to happen
// setVisible(false) about to happen
// setVisible(false) has just happened
// even when the dialog stays visible.

package modalproblemdemo;

import java.awt.Frame;
import javax.swing.JDialog;
import javax.swing.SwingUtilities;

public class Main {
    public static void main(String[] args) {
        final Dialogs d = new Dialogs();
        new Thread() {
            @Override
            public void run() {
                d.show();
                d.hide();
            }
        }.start();
    }

    static class Dialogs {
        final JDialog dialog;

        public Dialogs() {
            dialog = new JDialog((Frame) null, "Hello World", /*modal*/ true);
            dialog.setSize(400, 200);
        }

        public void show() {
            SwingUtilities.invokeLater(new Runnable() { public void run() {
                dialog.setLocationRelativeTo(null);
                System.out.println("setVisible(true) about to happen");
                dialog.setVisible(true);
            }});
        }

        public void hide() {
            SwingUtilities.invokeLater(new Runnable() { public void run() {
                System.out.println("setVisible(false) about to happen");
                dialog.setVisible(false);
                System.out.println("setVisible(false) has just happened");
            }});
        }
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
Zarkonnen
  • 22,200
  • 14
  • 65
  • 81

4 Answers4

4

It is clearly some sort of race condition. I don't think it is as simple as Erick Robertson's answer. Dialog's show() code is quite complicated, it contains some special logic for being called from the event dispatch thread and it also posts events to the event queue. Maybe the order in which events are posted is somehow affected by thread delays.

Perhaps what you need is SwingUtilities.invokeAndWait(), this way you guarantee that setVisible(true) has finished execution before you call setVisible(false). As Skip Head pointed out, invokeAndWait will block until the dialog is closed.

And why do you need it anyways?

EDIT: This is my scenario of what's happening:

  1. you call d.show() that posts setVisible(true) event
  2. the thread is put so sleep by the scheduler and EDT kicks in and starts executing the first event
  3. EDT gets kicked out before the first task has finished and posted an actual event that shows the dialog
  4. your thread executes d.hide() that posts the setVisible(false) event. The thread is finished and EDT kicks in
  5. EDT finished the first task, puts its showing event to the event queue
  6. It goes to the next event, and, voila, it is the setVisible(false) event!
  7. It messes up the whole state of the dialog and it stays visible and unresponsive.

EDIT2: Looks like ProgressMonitor has the functionality you are trying to implement.

Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68
  • InvokeAndWait in show() causes the modal dialog to block waiting to be closed. I think this is what Zarkonnen is trying to avoid with all the extra threads. – Skip Head Oct 17 '10 at 17:30
  • I need it as a "please wait, processing" modal dialog, to tell the user the program is busy working. It needs to be modal to stop the user from clicking buttons while the program is busy ('cause then both the EDT and the worker thread would be manipulating state). – Zarkonnen Oct 18 '10 at 07:31
  • @Zarkonnen: and the event is sometimes very short and causes a race condition? Try to put both methods in a `synchronized` block, maybe it will work. – Denis Tulskiy Oct 18 '10 at 07:56
  • @tulskiy ProgressMonitor's window is actually not modal! – Zarkonnen Oct 18 '10 at 08:39
2

So it turns out that what happens when you show()/setVisible(true) a modal dialog is that a second event dispatch loop is run within the call to show/setVisible. Which makes perfect sense once you know about it. With that in mind, I ended up with this code:

public void runBlockingTask(final String taskName, final BlockingTask bt) {
    SwingUtilities.invokeLater(new Runnable() { public void run() {
        new Thread("Worker Thread: " + taskName) {
            @Override
            public void run() {
                bt.run();
                progressDialog.setVisible(false);
            }
        }.start();
    }});
    // NB This causes the event dispatch loop to be run inside this call,
    // which is why we need  to put everything after setVisible into an
    // invokeLater.
    progressDialog.setVisible(true);
}
Zarkonnen
  • 22,200
  • 14
  • 65
  • 81
1

You could try to dispose() the dialog instead of hiding it, but that would require you to rebuild it if you wanted to show it again.

Qwerky
  • 18,217
  • 6
  • 44
  • 80
0

A little sleep time (100ms) between setVisible(true) and setVisible(false) solves the problem in some cases. see also https://bugs.openjdk.java.net/browse/JDK-5109571 And when trying to use dispose instead of setVisible(false), no race condition seems to occur