Keep your JFrame's default close operation to JFrame.DO_NOTHING_ON_CLOSE, but listen for closing attempt with a WindowListener (or more succinctly, a WindowAdapter).
You could use the same AbstractAction for both the menu item and the button, and then call the action's method in a WindowListener. For example
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.*;
public class ClosingJFrame {
public static void main(String[] args) {
final JFrame frame = new JFrame("My Frame");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
final CloseAction closeAction = new CloseAction(frame);
JPanel panel = new JPanel();
panel.add(new JButton(closeAction));
JMenuItem exitMenuItem = new JMenuItem(closeAction);
JMenu menu = new JMenu("File");
menu.setMnemonic(KeyEvent.VK_F);
menu.add(exitMenuItem);
JMenuBar menuBar = new JMenuBar();
menuBar.add(menu);
frame.setJMenuBar(menuBar);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
closeAction.confirmClosing();
}
});
frame.add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
}
class CloseAction extends AbstractAction {
private JFrame mainFrame;
public CloseAction(JFrame mainFrame) {
super("Exit");
putValue(MNEMONIC_KEY, KeyEvent.VK_X);
this.mainFrame = mainFrame;
}
@Override
public void actionPerformed(ActionEvent e) {
confirmClosing();
}
public void confirmClosing() {
int confirmed = JOptionPane.showConfirmDialog(mainFrame,
"Are you sure you want to quit?", "Confirm quit",
JOptionPane.YES_NO_OPTION);
if (confirmed == JOptionPane.YES_OPTION) {
// clean up code
System.exit(0);
}
}
}
Edit
Oops, I forgot your ctrl-Q keystroke bit. Use the same Action for that, and bind it to the ctrl-q key using KeyBindings. The improved code:
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.*;
public class ClosingJFrame {
public static void main(String[] args) {
final JFrame frame = new JFrame("My Frame");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
final CloseAction closeAction = new CloseAction(frame);
JPanel panel = new JPanel();
panel.add(new JButton(closeAction));
JMenuItem exitMenuItem = new JMenuItem(closeAction);
JMenu menu = new JMenu("File");
menu.setMnemonic(KeyEvent.VK_F);
menu.add(exitMenuItem);
JMenuBar menuBar = new JMenuBar();
menuBar.add(menu);
frame.setJMenuBar(menuBar);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
closeAction.confirmClosing();
}
});
// also use the same Action in your ctrl-q key bindings
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = panel.getInputMap(condition);
ActionMap actionMap = panel.getActionMap();
KeyStroke ctrlQKey = KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_DOWN_MASK);
inputMap.put(ctrlQKey, ctrlQKey.toString());
actionMap.put(ctrlQKey.toString(), closeAction);
frame.add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
}
class CloseAction extends AbstractAction {
private JFrame mainFrame;
public CloseAction(JFrame mainFrame) {
super("Exit");
putValue(MNEMONIC_KEY, KeyEvent.VK_X);
this.mainFrame = mainFrame;
}
@Override
public void actionPerformed(ActionEvent e) {
confirmClosing();
}
public void confirmClosing() {
int confirmed = JOptionPane.showConfirmDialog(mainFrame,
"Are you sure you want to quit?", "Confirm quit",
JOptionPane.YES_NO_OPTION);
if (confirmed == JOptionPane.YES_OPTION) {
// clean up code
System.exit(0);
}
}
}
Edit 2
This statement has me concerned:
then proceeds to dispose of all frames
as it implies that you have multiple JFrames. If so, you should read this link as it will explain why this is often not desired: The Use of Multiple JFrames, Good/Bad Practice?
Edit 3
As per Rob Camick's comment:
You could also just set an accelerator for the CloseAction by doing something like:
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control Q"));
Then the menu item will do the key bindings for you.
This would go into the CloseAction's constructor, like so:
public CloseAction(JFrame mainFrame) {
super("Exit");
putValue(MNEMONIC_KEY, KeyEvent.VK_X);
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control Q"));
this.mainFrame = mainFrame;
}