8

I have a frame, and want to prompt when the user closes it to save the document. But if they cancel, the frame shouldn't close.

frame.addWindowListener(new SaveOnCloseWindowListener(fileState));
...
public class SaveOnCloseWindowListener extends WindowAdapter {
    private final FileState fileState;

    public SaveOnCloseWindowListener(FileState fileState) {
        this.fileState = fileState;
    }

    public void windowClosing(WindowEvent e) {
        if (!fileState.onQuit())
            cancelClose();  
    }
}

FileState looks at whether the document is dirty. If it isn't it does nothing and returns true. If it is dirty, it asks the user if he wants to save (YES/NO/CANCEL). If the user cancels at this point, it should abort the windowClosing.

All the suggestions I've seen on the net involve explicitly exiting in the windowClosing method, thus overriding the use of JFrame.setDefaultCloseOperation(), and duplicating the code in JFrame.processWindowEvent().

I actually have a dirty solution, but would like to see if there are any cleaner ones.

Cheers

Duncan McGregor
  • 17,665
  • 12
  • 64
  • 118
  • possible duplicate of [How can I stop the closing of a Java application](http://stackoverflow.com/questions/8469097/how-can-i-stop-the-closing-of-a-java-application) – Andrew Thompson Mar 17 '13 at 00:16

4 Answers4

15

The right way is set JFrame.setDefaultCloseOperation to DO_NOTHING_ON_CLOSE when the window is created. And then just calling setVisible(false) or dispose() when your user accepts the close, or doing nothing when the close isn't accepted.

The whole purpose of JFrame.setDefaultCloseOperation is only to prevent the need to implement WindowListeners for the most simple actions. The actions performed by these default close operations are very simple.

EDIT:

I've added the solution I'm describing. This assumes you want the frame to be fully deleted.

frame.setDefaultCloseOperation(setDefaultCloseOperation);
frame.addWindowListener(new SaveOnCloseWindowListener(fileState));
...

public class SaveOnCloseWindowListener extends WindowAdapter {
    private final FileState fileState;

    public SaveOnCloseWindowListener(FileState fileState) {
        this.fileState = fileState;
    }

    public void windowClosing(WindowEvent e) {
        if (fileState.onQuit())
            frame.dispose();
    }
}
Thirler
  • 20,239
  • 14
  • 63
  • 92
  • But then my listener would need to know what the desired operation was. I suppose I could pass it the default close operation and duplicate the code in processWindowEvent, but it seems a shame. – Duncan McGregor Sep 23 '10 at 11:46
  • If you mean the desired operation by the user, then yes. In the listener you would have to pop up the dialog. So if I understand what you want you correctly: you should not ask what the user wants until you get the close event. – Thirler Sep 23 '10 at 11:51
  • Indeed - I've edited the question to make this clearer I hope. – Duncan McGregor Sep 23 '10 at 12:06
  • 1
    Actually my answer still applies then. The only change needed to your given example is that you need to call `setDefaultCloseOperation` on creation to prevent it from doing anything. And instead of canceling the close you explicitly close the window when you think it is required. – Thirler Sep 23 '10 at 12:29
  • Reading your answer I do like "The whole purpose of JFrame.setDefaultCloseOperation is only to prevent the need to implement WindowListeners for the most simple actions." This would be a lot easier to express if JFrame processWindowEvent delegated to a processCloseOperation that I could call. – Duncan McGregor Sep 24 '10 at 16:24
  • I've accepted this answer to give Thirler the credit, and so that the question doesn't remain open. Personally I'm staying with my ugly exception hack, you can take your pick of the 2. – Duncan McGregor Sep 28 '10 at 15:47
1

Closing an Application might make the process a little easier for you.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Thanks, but I understand how the stuff works (I've been programming in Swing since import com.sun.java.swing.*). I'm trying to find an elegant way of solving the problem. – Duncan McGregor Sep 24 '10 at 16:20
  • I thought I gave an elegant solution. It manages "prompts" and the "close operation" for you. I guess I don't understand what your concern is. – camickr Sep 24 '10 at 16:56
  • I have to confess that I didn't click through to your code. Now that I have - ExitAction doesn't actually exit, it closes the active frame, which may or may not exit the app. – Duncan McGregor Sep 24 '10 at 17:04
  • And I can't decide if I like CloseListener manipulating defaultCloseOperation on the fly. It feels wrong to me, but I can see that it does the job. So thanks, I'll try to be a little less ungracious ;-) – Duncan McGregor Sep 24 '10 at 17:06
0

I think that this is the logical expression of Thirler's answer, and kind of what I was trying to avoid.

frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new SaveOnCloseWindowListener(fileState, JFrame.EXIT_ON_CLOSE));

public class SaveOnCloseWindowListener extends WindowAdapter {

    private final int closeOperation;
    private final Document document;

    public SaveOnCloseWindowListener(int closeOperation, Document document) {
        this.closeOperation = closeOperation;
        this.document = document;
    }

    public void windowClosing(WindowEvent e) {
        if (!document.onClose())
            doClose((Window) e.getSource());
    }

    private void doClose(Window w) {
        switch (closeOperation) {
        case JFrame.HIDE_ON_CLOSE:
            w.setVisible(false);
            break;
        case JFrame.DISPOSE_ON_CLOSE:
            w.dispose();
            break;
        case JFrame.DO_NOTHING_ON_CLOSE:
        default:
            break;
        case JFrame.EXIT_ON_CLOSE:
            System.exit(0);
            break;
        }
    }
}
Duncan McGregor
  • 17,665
  • 12
  • 64
  • 118
  • The reason I don't like this is because it duplicates the code in JFrame, and requires the frame subclass to know what the required operation is when it creates the window listener. I suppose I could override setDefaultCloseOperation to pass the operation down to the listener... – Duncan McGregor Sep 23 '10 at 12:34
  • I will edit my answer with the solution I mean. In general you are the one that makes the JFrame, so you know which close operation you want, in essence you disable the close operation. – Thirler Sep 23 '10 at 12:35
  • I think that's where we differ. I want a JFrame that is able to veto closes if the user says that they don't want to close it, but where the default close operation is a policy of the application, not the frame. – Duncan McGregor Sep 24 '10 at 16:28
0

Thanks to the input from Thirler and camickr. This is my solution, which I'm sure that some will hate, but avoids duplicating logic in JFrame, and allows client code to setDefaultCloseOperation as usual.

class MyFrame {        
    @Override protected void processWindowEvent(WindowEvent e) {
        try {
            super.processWindowEvent(e);
        } catch (SaveOnCloseWindowListener.VetoException x) {}
    }
}

public class SaveOnCloseWindowListener extends WindowAdapter {

    public static class VetoException extends RuntimeException {
    }

    private final DocumentController documentController;

    public SaveOnCloseWindowListener(DocumentController documentController) {
        this.documentController = documentController;
    }

    public void windowClosing(WindowEvent e) {
        if (!documentController.onClose())
            throw new VetoException();
    }
}
Duncan McGregor
  • 17,665
  • 12
  • 64
  • 118