3

I have several windows that I want to save defaults from, after the time the user takes action to close the window (by the button in the title bar, a menu item or a button I've provided) and before it's disposed of.

  • Some windows I can DISPOSE_ON_CLOSE but some I need info from before they're disposed().
  • formWindowClosed seems only to fire when the window is disposed of, which I can't always rely on (see above & below)
  • formWindowClosing seems to fire only when the user closes the window from the titlebar's system menu, even though I call System.exit(0) in my own menu action handler
  • According do GC documentation, the dispose() method is not always called and is frequently ignored on app shutdown.
  • I've added a ShutdownHook to run System.runFinalization() but the code is still not being executed. This may be too late anyways, as some windows will have been disposed of by then.

How can I ensure that the code is run before the window is disposed of? It is a task that the window should be able to take care of, itself. I'm kind of frustrated at the unreliability of the Closed, Closing and dispose events. What am I missing?

user1803551
  • 12,965
  • 5
  • 47
  • 74
Masked Coder
  • 280
  • 2
  • 15
  • 3
    Just found the answer to my own question: Rob Camick wrote a [nice article](https://tips4java.wordpress.com/2009/05/01/closing-an-application/) that provides two Listener classes that cover all the bases. – Masked Coder May 10 '15 at 17:46
  • 1
    You can [answer your own question](http://meta.stackoverflow.com/q/17463/163188). See also [*Swing on OSX: How to Trap command-Q?*](http://stackoverflow.com/q/2061194/230513). – trashgod May 10 '15 at 23:35

2 Answers2

0

Use WindowListener windowClosed() method. In general in Java you cannot rely on disposition. It is not guaranteed that this method will be called.

Alex
  • 4,457
  • 2
  • 20
  • 59
  • how is that different from the second point in my original post? windowClosed() does not fire if I provide my own button or menu item. – Masked Coder May 10 '15 at 19:28
  • You had called it in funny name (even link appears right), so I'd missed it. In general - use all methods because they fire on different events like Closed and Closing. Also, may I suggest, that you add logging to each method? It could be too quick and you may miss it. – Alex May 10 '15 at 20:11
0

After finding a number of similar questions here:

I created an app that fired System.out.println() statements on each of windowDeactivated, windowClosing and WindowClosed events and tried closing both a JFrame and a JDialog window with the system X button and with a button that just setVisible(false):

/**
* Test program to explore the relationship between defaultCloseOperation 
* states, and the sequence of events triggered when closing a window 
* with the (X) button vs using setVisible(false).
* 
* @author MaskedCoder
*
*/

package testwindowadapter;

import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.WindowConstants;

/**
* Class to listen for window close events
*/
public class WindowNotifier extends WindowAdapter implements WindowListener
{
    public WindowNotifier() {
        super();
    }

    @Override
    public void windowClosing(WindowEvent e)
    {
        super.windowClosing(e);
        System.out.println(e.getComponent().getClass().getSimpleName() + ".windowClosing fired");
    }

    @Override
    public void windowClosed(WindowEvent e) {
        super.windowClosed(e); //To change body of generated methods, choose Tools | Templates.
        System.out.println(e.getComponent().getClass().getSimpleName() + ".windowClosed fired");
    }

    @Override
    public void windowDeactivated(WindowEvent e) {
        super.windowDeactivated(e); 
        System.out.println(e.getComponent().getClass().getSimpleName() + ".windowDeactivated fired");
    }
}

/**
* Creates new form TestDialog
*/
public class TestDialog extends javax.swing.JDialog {

    public TestDialog(java.awt.Frame parent, boolean modal) {
        super(parent, modal);
        initComponents();
        addWindowListener(new WindowNotifier());
        cboDefaultCloseOp.setSelectedIndex(getDefaultCloseOperation());
    }

    /**
    * This method is called from within the constructor to initialize the form.
    * WARNING: Do NOT modify this code. The content of this method is always
    * regenerated by the Form Editor.
    */
    @SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">                          
private void initComponents() {

    btnClose = new javax.swing.JButton();
    cboDefaultCloseOp = new javax.swing.JComboBox();

    setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
    setTitle("WindowAdapter Test");

    btnClose.setText("Close window");
    btnClose.addActionListener(new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
        btnCloseActionPerformed(evt);
    }
    });

    cboDefaultCloseOp.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "DO_NOTHING_ON_CLOSE", "HIDE_ON_CLOSE", "DISPOSE_ON_CLOSE" }));
    cboDefaultCloseOp.addItemListener(new java.awt.event.ItemListener() {
    public void itemStateChanged(java.awt.event.ItemEvent evt) {
        cboDefaultCloseOpItemStateChanged(evt);
    }
    });

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
    layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(cboDefaultCloseOp, javax.swing.GroupLayout.PREFERRED_SIZE, 225, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addGroup(layout.createSequentialGroup()
            .addGap(58, 58, 58)
            .addComponent(btnClose)))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );
    layout.setVerticalGroup(
    layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(cboDefaultCloseOp, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addGap(18, 18, 18)
        .addComponent(btnClose)
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );

    pack();
}// </editor-fold>                        

private void btnCloseActionPerformed(java.awt.event.ActionEvent evt) {                                         
    setVisible(false);
}                                        

private void cboDefaultCloseOpItemStateChanged(java.awt.event.ItemEvent evt) {                                                   
    setDefaultCloseOperation(cboDefaultCloseOp.getSelectedIndex());
}                                                  

// Variables declaration - do not modify                     
private javax.swing.JButton btnClose;
private javax.swing.JComboBox cboDefaultCloseOp;
// End of variables declaration                   
}

/**
* Creates new form TestFrame
*/
public class TestFrame extends javax.swing.JFrame {

    public TestFrame() {
        super();
        initComponents();
        addWindowListener(new WindowNotifier());
        cboDefaultCloseOp.setSelectedIndex(getDefaultCloseOperation());
    }

    /**
    * This method is called from within the constructor to initialize the form.
    * WARNING: Do NOT modify this code. The content of this method is always
    * regenerated by the Form Editor.
    */
    @SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">                          
private void initComponents() {

    cboDefaultCloseOp = new javax.swing.JComboBox();
    btnClose = new javax.swing.JButton();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

    cboDefaultCloseOp.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "DO_NOTHING_ON_CLOSE", "HIDE_ON_CLOSE", "DISPOSE_ON_CLOSE", "EXIT_ON_CLOSE" }));
    cboDefaultCloseOp.addItemListener(new java.awt.event.ItemListener() {
    public void itemStateChanged(java.awt.event.ItemEvent evt) {
        cboDefaultCloseOpItemStateChanged(evt);
    }
    });

    btnClose.setText("Close window");
    btnClose.addActionListener(new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
        btnCloseActionPerformed(evt);
    }
    });

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
    layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(cboDefaultCloseOp, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addGroup(layout.createSequentialGroup()
            .addGap(41, 41, 41)
            .addComponent(btnClose)))
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );
    layout.setVerticalGroup(
    layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(cboDefaultCloseOp, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addGap(18, 18, 18)
        .addComponent(btnClose)
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );

    pack();
}// </editor-fold>                        

private void btnCloseActionPerformed(java.awt.event.ActionEvent evt) {                                         
    setVisible(false);
}                                        

private void cboDefaultCloseOpItemStateChanged(java.awt.event.ItemEvent evt) {                                                   
    setDefaultCloseOperation(cboDefaultCloseOp.getSelectedIndex());
}                                                  

// Variables declaration - do not modify                     
private javax.swing.JButton btnClose;
private javax.swing.JComboBox cboDefaultCloseOp;
// End of variables declaration                   
}

public class TestWindowAdapter {

    public TestWindowAdapter() {

    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
//              TestDialog MainWin = new TestDialog(null, true);
                TestFrame MainWin = new TestFrame();
                MainWin.setVisible(true);
            }
        });
    }

}

From that, I created a WindowListener that reliably fired once and only once, optionally asking for permission before allowing closure. It works for JFrame and JDialog. I chose to define "closed" as any time that the window goes from visible to not visible and "opened" as any time that the window goes from not visible to visible. This will not include iconification / deiconification. windowClosing duties will not be performed if the confirmClose denies permission to close thus preventing the window from becoming invisible.

/**
 *
 * @author MaskedCoder
 */
package testwindowadapter;

import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

public class ReliableOneShotCloseListener extends WindowAdapter implements WindowListener {
    public interface CloseDuties {
        public boolean confirmClose(WindowEvent e);
        public void windowClosing(WindowEvent e);
    }

    private CloseDuties closeDuties;
    private int defaultCloseOperation;
    private boolean windowClosingFired = false;

    public ReliableOneShotCloseListener(int iniDefaultCloseOperation, CloseDuties iniCloseDuties) {
        super();
        closeDuties = iniCloseDuties;
        defaultCloseOperation = iniDefaultCloseOperation;
    }

    private int getDefaultCloseOperation(WindowEvent e) {
        if(e.getComponent() instanceof JFrame) {
            return ((JFrame) e.getComponent()).getDefaultCloseOperation();
        } 
        else if(e.getComponent() instanceof JDialog) {
            return ((JDialog) e.getComponent()).getDefaultCloseOperation();
        }
        else throw new IllegalArgumentException("WindowEvent.getComponent() is " + e.getComponent().getClass().getSimpleName() + ", must be JFrame or JDialog.");
    }

    private void setDefaultCloseOperation(WindowEvent e, int newDefaultCloseOperation) {
        if(e.getComponent() instanceof JFrame) {
            ((JFrame) e.getComponent()).setDefaultCloseOperation(newDefaultCloseOperation);
        } 
        else if(e.getComponent() instanceof JDialog) {
            ((JDialog) e.getComponent()).setDefaultCloseOperation(newDefaultCloseOperation);
        }
        else throw new IllegalArgumentException("WindowEvent.getComponent() is " + e.getComponent().getClass().getSimpleName() + ", must be JFrame or JDialog.");
    }

    private void performCloseDuties(WindowEvent e) {
        if(!windowClosingFired) {
            if(closeDuties.confirmClose(e)) {
                setDefaultCloseOperation(e, defaultCloseOperation);
                closeDuties.windowClosing(e);
                windowClosingFired = true;
            }
            else
                setDefaultCloseOperation(e, WindowConstants.DO_NOTHING_ON_CLOSE);
        }
    }

    public int getDefaultCloseOperation() {
        return defaultCloseOperation;
    }

    public void setDefaultCloseOperation(int newDefaultCloseOperation) {
        defaultCloseOperation = newDefaultCloseOperation;
    }

    @Override
    public void windowOpened(WindowEvent e) {
        windowClosingFired = false;
    }

    @Override
    public void windowClosing(WindowEvent e) {
        performCloseDuties(e);
    }

    @Override
    public void windowClosed(WindowEvent e) {
        performCloseDuties(e);
    }

    @Override
    public void windowActivated(WindowEvent e) {
        windowClosingFired = false;
    }

    @Override
    public void windowDeactivated(WindowEvent e) {      
        if(!e.getComponent().isVisible()) 
            performCloseDuties(e);
    }
}

Adding this as a windowlistener to a JDialog or JFrame extension and implementing a version of CloseDuties adds a great deal of flexibility and reliability to the business of closing a window.

Community
  • 1
  • 1
Masked Coder
  • 280
  • 2
  • 15