Search code examples
javaimagej

How to force ImageJ to close all its windows without close event error?


I am writing a Java application for image analysis which at one point opens ImageJ with

ImageJ ij = new ImageJ();

and also opens a Windows containing an ImagePlus.

Now, whenever one closes ImageJ first, the ImagePlus will not close when pushing the close button. The other way around works, however in both cases an exception is thrown after closing ImageJ:

java.lang.reflect.InvocationTargetException
    at java.awt.EventQueue.invokeAndWait(EventQueue.java:1288)
    at java.awt.Window.doDispose(Window.java:1209)
    at java.awt.Window.dispose(Window.java:1147)
    at ij.ImageJ.run(ImageJ.java:784)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalArgumentException: null source
    at java.util.EventObject.<init>(EventObject.java:56)
    at java.awt.AWTEvent.<init>(AWTEvent.java:337)
    at java.awt.event.InvocationEvent.<init>(InvocationEvent.java:285)
    at java.awt.event.InvocationEvent.<init>(InvocationEvent.java:174)
    at sun.awt.X11.XBaseMenuWindow.dispose(XBaseMenuWindow.java:907)
    ...

I don't know whether it is related as it happens in both cases.

Any suggestions on how to force ImageJ to close all its windows?


Solution

  • The exception

    This happens when using OpenJDK 7 on Linux. The exception is fixed in Java 8.

    Also: note that that exception is not the actual cause of the quitting issue you are seeing.

    The disposal problem

    ImageJ 1.x's application disposal is a convoluted mess. (See this news post for some technical discussion.) It was really intended primarily to run as a standalone application, and is mostly tested with the exitWhenQuitting flag set to true such that the JVM shuts down upon closure of the main window. So it is not surprising that using ImageJ in a different fashion results in hanging image windows.

    I have tested various workarounds—e.g.:

    ij.addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(final WindowEvent e) {
            // dispose all image windows
            for (final int id : WindowManager.getIDList()) {
                final ImagePlus imp = WindowManager.getImage(id);
                if (imp == null) continue;
                final ImageWindow win = imp.getWindow();
                if (win != null) win.dispose();
            }
            // dispose all other ImageJ windows
            for (final Window w : WindowManager.getAllNonImageWindows()) {
                w.dispose();
            }
        }
    });
    

    But none of them work as one might hope. It cost me weeks of development and experimentation to make quitting work as we wanted in ImageJ2, according to the news posted linked above.

    Here is some code using ImageJ2 that almost behaves the way you want:

    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.WindowConstants;
    
    import net.imagej.ImageJ;
    
    public class IJDispose {
    
      public static void main(final String... args) {
        final ImageJ ij = new ImageJ();
        ij.ui().showUI();
    
        final JFrame frame = new JFrame("Hello");
        final JButton b = new JButton("Close ImageJ");
        b.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(final ActionEvent e) {
            ij.getContext().dispose();
          }
        });
        frame.getContentPane().add(b);
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
      }
    
    }
    

    After launching it, press Shift+B to open the Blobs sample image. Then click the "Close ImageJ" button from the non-ImageJ frame. You'll see that the ImageJ main window and the image window dispose as desired (using this code from ImageJ Legacy).

    However, there are (at least) three problems:

    1. This example does not hook up the ij.getContext().dispose() call to the actual ImageJ1 UI window closing event. And doing that would not be trivial (I say without having dug deeply in this code recently).

    2. After disposing ImageJ, as well as the extra JFrame, the JVM is supposed to shut down. We put a lot of effort into making it do so, actually. But it actually doesn't with the current version of ImageJ, presumably due to some undisposed resource(s) somewhere. This is a bug.

    3. Clicking the X on the main ImageJ window shuts down the entire JVM, because ImageJ1's exitWhenQuitting flag gets set to true. You could toggle it back to false yourself, but this is actually tricky due to class loading issues relating to the fact that ImageJ2 patches ImageJ1 at runtime using Javassist.

    The next question is: How badly do you really need this to work?