left.shift
left.shift

Reputation: 43

Java Swing Dialog, how to call wait method when capturing WindowClosing event

I have a JRuby script that opens a Java dialog to report on the progress of the script.

I am capturing a windowclosing event, and want the dialog to wait until some cleanup in the JRuby script has occurred, and then dispose. Instead, the dialog just hangs when the user presses the top right red x button.

How do I correctly call the wait method to wait on that flag change? Am I using the lock object correctly?

A jruby script calls this dialog.
If a user presses the top right red X, the dialog captures the windowclosing event and sets a 'cancelled' flag. The script keeps an eye on that flag, then starts shutting down some long running tasks and what not . When done, it updates a flag on the dialog to say cleanup has occurred. Meanwhile, the dialog is looping, waiting on that flag to change. Then it calls dispose().

I've tried using sleep. For some reason, that upsets something between my JRuby and dialog, cleanup occurs okay, but the dialog does not dispose.

Using wait, with synchronize(this) generates a IllegalMonitorException, but the script cleans up and the dialog does dispose correctly apart from the exception.

Have looked at a bunch of other posts on how to synchorize the wait method, would very much like to understand this.

Thanks very much for any assistance.

Dialog class as follows:

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

public class MyDialog extends JDialog {
private boolean userCancelled;
private boolean scriptCleanedUp;

//private static Object lock = new Object();

public MyDialog(lock) {
    userCancelled = false;
    scriptCleanedUp = false;
    setDefaultCloseOperation(2);

    //[..] add various controls to dialog
    addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent we) {
            // the jruby script keeps an eye on this flag to see if the user has cancelled the dialog
            userCancelled = true;

            /* once cancelled, wait for script to flag that it has performed its cleanup
            */

            /* here is the problem area, what do I need to synchronize to use the wait method?
            */
            while (!scriptCleanedUp) {
                try {
                      synchronized (lock) {
                        lock.wait(1000000000);
                    }

                   // Thread.sleep(1000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            dispose();
        }
    });
    super.paint(super.getGraphics());
}
public boolean user_cancelled() { return userCancelled; }
public void setScriptCleanedUpToTrue() { this.scriptCleanedUp = true; }

public static void forBlock(MyDialogBlockInterface block)
{
    MyDialog dialog = new MyDialog(new Object());
    dialog.setVisible(true);
    block.DoWork(dialog);
    dialog.dispose();
}
}

And if it helps, this is how the JRuby script calls the dialog

MyDialog.forBlock do |dialog|
    #do long running jruby task here
end

Upvotes: 2

Views: 348

Answers (1)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285405

You've got a lot of problems with that code including:

  • You're making long-running calls on the Swing event thread, something that will tie up this critical thread and is thus guaranteed to freeze your GUI.
  • Your calling paint(...) directly, something that should almost never be done.

I'll bet that most of your problem could be called by just making sure that your dialog is a modal JDialog, if you make long-running calls in a background thread such as with a SwingWorker, and rather than trying to wait for a lock to be released, use a call-back mechanism to notify the dialog to shut itself down.

For example:

import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.*;

/**
 * http://stackoverflow.com/a/29933423/522444
 * @author Pete
 *
 */
@SuppressWarnings("serial")
public class TestMyDialog2 extends JPanel {
   private static final int PREF_W = 400;
   private static final int PREF_H = PREF_W;

   public TestMyDialog2() {
      add(new JButton(new MyDialogAction("Please press this button!", this)));
   }

   @Override
   public Dimension getPreferredSize() {
      if (isPreferredSizeSet()) {
         return super.getPreferredSize();
      }
      // let's make this reasonably big
      return new Dimension(PREF_W, PREF_H);
   }

   private static void createAndShowGui() {
      TestMyDialog2 mainPanel = new TestMyDialog2();

      JFrame frame = new JFrame("TestMyDialog2");
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }

}

@SuppressWarnings("serial")
class MyDialogAction extends AbstractAction {
   private JDialog dialog;
   private MyWorker myWorker;
   private TestMyDialog2 testMyDialog2;

   public MyDialogAction(String name, TestMyDialog2 testMyDialog2) {
      super(name);
      int mnemonic = (int) name.charAt(0);
      putValue(MNEMONIC_KEY, mnemonic);
      this.testMyDialog2 = testMyDialog2;
   }

   public void dialogIsClosing(WindowEvent e) {
      if (myWorker != null && !myWorker.isDone()) {
         myWorker.setKeepRunning(false);
      } else {
         if (dialog != null && dialog.isVisible()) {
            dialog.dispose();
         }
      }
   }

   @Override
   public void actionPerformed(ActionEvent e) {
      Window mainGui = SwingUtilities.getWindowAncestor(testMyDialog2);
      dialog = new JDialog(mainGui, "My Dialog", ModalityType.APPLICATION_MODAL);
      dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
      dialog.add(Box.createRigidArea(new Dimension(200, 100)));
      dialog.addWindowListener(new DialogWindowListener(this));
      dialog.pack();

      myWorker = new MyWorker();
      myWorker.addPropertyChangeListener(new MyWorkerListener(dialog));
      myWorker.execute();
      dialog.setLocationRelativeTo(mainGui);
      dialog.setVisible(true);
   }
}

class MyWorker extends SwingWorker<Void, Void> {
   private volatile AtomicBoolean keepRunning = new AtomicBoolean(true);

   @Override
   protected Void doInBackground() throws Exception {
      // to emulate long-running code
      while (keepRunning.get()) {
         Thread.sleep(200);
         System.out.println("Long running background code is running");
      }

      System.out.println("Doing shut-down process. Will close in 10 seconds");
      for (int i = 0; i < 10; i++) {
         System.out.println("Countdown: " + (10 - i));
         Thread.sleep(1000); // emulate a long running shut-down process
      }
      return null;
   }

   public void setKeepRunning(boolean newValue) {
      this.keepRunning.getAndSet(newValue);
   }
}

class MyWorkerListener implements PropertyChangeListener {
   private JDialog dialog;

   public MyWorkerListener(JDialog dialog) {
      this.dialog = dialog;
   }

   @Override
   public void propertyChange(PropertyChangeEvent evt) {
      if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
         dialog.dispose();
         try {
            ((MyWorker) evt.getSource()).get();
         } catch (InterruptedException e) {
            e.printStackTrace();
         } catch (ExecutionException e) {
            e.printStackTrace();
         }
      }
   }
}

class DialogWindowListener extends WindowAdapter {
   private MyDialogAction myDialogAction;

   public DialogWindowListener(MyDialogAction myDialogAction) {
      this.myDialogAction = myDialogAction;
   }

   @Override
   public void windowClosing(WindowEvent e) {
      myDialogAction.dialogIsClosing(e);
   }
}

Upvotes: 2

Related Questions