0

I want 3 ways of exiting the application

  1. Key stroke CTRL-Q
  2. Select 'Exit' from menu bar
  3. Close 'x' button on JFrame

What I've done so far is add the even listeners for the first two, but I cannot figure out how to have the JFrame close 'x' button to do the same thing. For now it just exits the application without prompting a confirmation, because it doesn't know how to get to that. I basically want all the frames to be disposed, after the user has confirmed that they want to exit. This happens in the first two cases, simply because their action listeners call an exit() method that confirms exiting, and then proceeds to dispose of all frames.

public class MainWindow extends JFrame {
    ...
    this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

    this.addWindowListener(new WindowListener() {
        @Override
        public void windowClosing(WindowEvent e) {
            System.exit(0);
        }
    }

    ...

    // this is part of the main menu bar (case #2)
    JMenuItem mntmExit = new JMenuItem("Exit");
    mntmExit.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            MainWindow.this.exit();
        }

    });

    // this is case #1
    KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {

        @Override
        public boolean dispatchKeyEvent(KeyEvent e) {
            if(e.getKeyCode() == KeyEvent.VK_Q && e.getModifiers() == InputEvent.CTRL_MASK) {
                MainWindow.this.exit();
            }

            return false;
        }

    });
}

// the exit method
private void exit() {
    int confirmed = JOptionPane.showConfirmDialog(this, "Are you sure you want to quit?", "Confirm quit", JOptionPane.YES_NO_OPTION);

    if(confirmed == JOptionPane.YES_OPTION) {
        Frame[] frames = Frame.getFrames();

        for(Frame frame : frames) {
            frame.dispose();
        }
    }
}

Is it possible to assign an action listener to the close button? If not, is there another way I should approach this?

cj5
  • 785
  • 3
  • 12
  • 34
  • 1
    See [Closing an Application](http://tips4java.wordpress.com/2009/05/01/closing-an-application/) for a simple interface that may make implementing these requirements easier as you will only need to write a custom Action. – camickr Sep 21 '14 at 01:05

2 Answers2

4

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;
   }
Community
  • 1
  • 1
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • 1
    +1. 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. – camickr Sep 21 '14 at 00:41
2

In your windowClosing method, call your exit() method before System.exit().

That will automatically close out your java program when you click the X.

DMP
  • 973
  • 1
  • 7
  • 13