user3188481
user3188481

Reputation: 45

Key listener and action performed

Is it possible to have a key event in a action performed method as I am using action listener for gui but I want to close my app on a certain key press

Upvotes: 2

Views: 2402

Answers (1)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285403

You could use an AbstractAction, which is like an ActionListener on steroids, then assign it to your JButton as it's Action via the button's setAction(...) method. Then I would use Key Bindings, not a KeyListener as this will allow us to get around the KeyListener component focus issue with ease, and bind the very same AbstractAction to your key press using the key bindings.

Note that if you set the JButton's or the AbstractAction's mnemonic properly, then an alt-key combination will automatically work in pressing the button.

This one below may be over-kill for you, but it allows this same action to be used in Key Bindings, a JButton or a JMenuItem, which is a little tricky to do.

// class of mine to allow disposing of a window
// It's a little complex to allow it to work with either a JButton
// or a JMenuItem
@SuppressWarnings("serial")
class ExitAction extends AbstractAction {
    public ExitAction() {
        super("Exit");
        putValue(MNEMONIC_KEY, KeyEvent.VK_X);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // get the button that caused this action
        Object source = e.getSource();
        Window topWindow = null;
        if (source instanceof AbstractButton) {
            AbstractButton exitButton = (AbstractButton) source;

            // get the parent top level window
            topWindow = SwingUtilities.getWindowAncestor(exitButton);
            if (topWindow == null) { // if null, then likely in a JMenuItem
                // so we have to get its jpopupmenu parent
                Container parent = exitButton.getParent();
                if (parent instanceof JPopupMenu) {
                    JPopupMenu popupMenu = (JPopupMenu) parent;

                    // get the invoker for the pop up menu
                    Component invoker = popupMenu.getInvoker();
                    if (invoker != null) {
                        // and get *its* top level window
                        topWindow = SwingUtilities.getWindowAncestor(invoker);
                    }
                }
            }
        } else if (source instanceof Component) {
            // if in key bindings
            topWindow = SwingUtilities.getWindowAncestor((Component) source);
        }
        if (topWindow != null) {
            // dispose of the top-level window
            topWindow.dispose();
        }
    }
}

This is from a demo program that I created for another StackOverflow question. Here's the code in its entirety. It also demonstrates use of CardLayout:

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

/**
 * https://stackoverflow.com/questions/30288947
 * /accessing-jcombobox-data-from-separate-jform
 * 
 * @author Pete
 *
 */
public class SimpleMultPanels {
    private static void createAndShowGui() {
        // create JFrame
        JFrame frame = new JFrame("SimpleMultPanels");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        // add our MainPanel to the JFrame
        frame.getContentPane().add(new MainPanel());
        frame.pack(); // pack it
        frame.setLocationByPlatform(true);
        frame.setVisible(true); // show it
    }

    public static void main(String[] args) {
        // this is for starting our Swing app on the event thread
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}

@SuppressWarnings("serial")
class MainPanel extends JPanel {
    private static final KeyStroke EXIT_KEY_STROKE = KeyStroke.getKeyStroke(
            KeyEvent.VK_Z, KeyEvent.CTRL_DOWN_MASK);;

    private CardLayout cardLayout = new CardLayout();

    // the three "card" JPanels that this JPanel displays:
    private MenuPanel menuPanel = new MenuPanel();
    private HoldsComboBoxPanel holdsComboBoxPanel = new HoldsComboBoxPanel();
    private ShowSelectionPanel showSelectionPanel = new ShowSelectionPanel();

    // Actions for our JButtons
    private ExitAction exitAction = new ExitAction();
    private ShowAction backToMenuAction = new ShowAction(this, "Back to Menu",
            MenuPanel.NAME, KeyEvent.VK_B);
    private ShowAction toHoldsComboAction = new ShowAction(this, "Combo Panel",
            HoldsComboBoxPanel.NAME, KeyEvent.VK_C);
    private ShowAction toShowSelectionAction = new ShowAction(this,
            "Show Selection", ShowSelectionPanel.NAME, KeyEvent.VK_S);

    public MainPanel() {
        // add an ActionListener to the JComboBox
        holdsComboBoxPanel.addComboBoxListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                // get the combo's selected String
                String selection = holdsComboBoxPanel.getComboSelection();
                if (selection != null) {
                    // push it into showSelection
                    showSelectionPanel.setDisplayText(selection);
                }
            }
        });

        // add Actions to class to allow swapping of cards and
        holdsComboBoxPanel.addButtonAction(backToMenuAction);
        holdsComboBoxPanel.addButtonAction(toShowSelectionAction);
        holdsComboBoxPanel.addButtonAction(exitAction); // and to exit gui

        showSelectionPanel.addButtonAction(backToMenuAction);
        showSelectionPanel.addButtonAction(toHoldsComboAction);
        showSelectionPanel.addButtonAction(exitAction);

        menuPanel.addButtonAction(toHoldsComboAction);
        menuPanel.addButtonAction(toShowSelectionAction);
        menuPanel.addButtonAction(exitAction);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        // set our layout
        setLayout(cardLayout);

        // and add our "card" JPanels
        add(menuPanel, MenuPanel.NAME);
        add(holdsComboBoxPanel, HoldsComboBoxPanel.NAME);
        add(showSelectionPanel, ShowSelectionPanel.NAME);

        setExitOnKeyStroke(EXIT_KEY_STROKE); // added 9/27/15
    }

    private void setExitOnKeyStroke(KeyStroke keyStroke) {
        // do your key bindings

        // first get ActionMap and InputMap
        InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = getActionMap();

        // then the keystroke
        String key = keyStroke.toString();

        inputMap.put(keyStroke, key); // map the keystroke to a String key
        actionMap.put(key, exitAction); // map the same String key to the Action
    }

    // method that allows outside classes to swap views
    public void showCard(String key) {
        cardLayout.show(this, key);
    }
}

@SuppressWarnings("serial")
class HoldsComboBoxPanel extends JPanel {
    // constant String that will be used by the CardLayout
    public static final String NAME = "holds combobox panel";

    // sample data
    private static final String[] DATA = { "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday" };
    private JComboBox<String> comboBox = new JComboBox<>(DATA);

    // JPanel to hold buttons to allow moving between cards
    private JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 5, 0));

    public HoldsComboBoxPanel() {
        comboBox.setSelectedIndex(-1);
        JPanel centerPanel = new JPanel();
        centerPanel.add(comboBox);

        setLayout(new BorderLayout());
        add(centerPanel, BorderLayout.CENTER);
        add(buttonPanel, BorderLayout.SOUTH);
    }

    // method to add a listener
    public void addComboBoxListener(ActionListener listener) {
        comboBox.addActionListener(listener);
    }

    // getter method
    public String getComboSelection() {
        return (String) comboBox.getSelectedItem();
    }

    // create a new JButton with an Action and add to buttonPanel
    public void addButtonAction(Action action) {
        buttonPanel.add(new JButton(action));
    }
}

@SuppressWarnings("serial")
class ShowSelectionPanel extends JPanel {
    public static final String NAME = "show selection panel";
    private static final int PREF_W = 300;
    private static final int PREF_H = PREF_W;
    private JTextField displayField = new JTextField(10);
    private JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 5, 0));

    public ShowSelectionPanel() {
        displayField.setFocusable(false);

        JPanel centerPanel = new JPanel();
        centerPanel.add(displayField);

        setLayout(new BorderLayout());
        add(centerPanel, BorderLayout.CENTER);
        add(buttonPanel, BorderLayout.SOUTH);
    }

    // code to make our GUI a little larger
    @Override
    public Dimension getPreferredSize() {
        Dimension superSz = super.getPreferredSize();
        if (isPreferredSizeSet()) {
            return superSz;
        }
        int prefW = Math.max(superSz.width, PREF_W);
        int prefH = Math.max(superSz.height, PREF_H);
        return new Dimension(prefW, prefH);
    }

    // setter method
    public void setDisplayText(String text) {
        displayField.setText(text);
        // or do whatever else you want to do with the selection
    }

    public void addButtonAction(Action action) {
        buttonPanel.add(new JButton(action));
    }

}

@SuppressWarnings("serial")
class MenuPanel extends JPanel {
    public static final String NAME = "menu panel";
    private JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 0, 5));

    public MenuPanel() {
        setLayout(new GridBagLayout());
        add(buttonPanel);
    }

    public void addButtonAction(Action action) {
        buttonPanel.add(new JButton(action));
    }
}

@SuppressWarnings("serial")
class ShowAction extends AbstractAction {
    private MainPanel mainPanel;
    private String key;

    /**
     * Abstract Action used by JButtons
     * 
     * @param mainPanel
     *            : the JPanel that uses the CardLayout
     * @param name
     *            : The name displayed by the button
     * @param key
     *            : The key used in the CardLayout#show(String key) mehtod
     * @param mnemonic
     *            : the JButton's mnemonic char
     */
    public ShowAction(MainPanel mainPanel, String name, String key, int mnemonic) {
        super(name);
        putValue(MNEMONIC_KEY, mnemonic);
        this.mainPanel = mainPanel;
        this.key = key;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // tell the mainPanel to show the card associated with the key
        mainPanel.showCard(key);
    }
}

// class of mine to allow disposing of a window
// It's a little complex to allow it to work with either a JButton
// or a JMenuItem
@SuppressWarnings("serial")
class ExitAction extends AbstractAction {
    public ExitAction() {
        super("Exit");
        putValue(MNEMONIC_KEY, KeyEvent.VK_X);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // get the button that caused this action
        Object source = e.getSource();
        Window topWindow = null;
        if (source instanceof AbstractButton) {
            AbstractButton exitButton = (AbstractButton) source;

            // get the parent top level window
            topWindow = SwingUtilities.getWindowAncestor(exitButton);
            if (topWindow == null) { // if null, then likely in a JMenuItem
                // so we have to get its jpopupmenu parent
                Container parent = exitButton.getParent();
                if (parent instanceof JPopupMenu) {
                    JPopupMenu popupMenu = (JPopupMenu) parent;

                    // get the invoker for the pop up menu
                    Component invoker = popupMenu.getInvoker();
                    if (invoker != null) {
                        // and get *its* top level window
                        topWindow = SwingUtilities.getWindowAncestor(invoker);
                    }
                }
            }
        } else if (source instanceof Component) {
            // if in key bindings
            topWindow = SwingUtilities.getWindowAncestor((Component) source);
        }
        if (topWindow != null) {
            // dispose of the top-level window
            topWindow.dispose();
        }
    }
}

If you add this method in the MainPanel class and call it in MainPanel's constructor, you'll bind the exit action to a keystroke of choice:

    private void setExitOnKeyStroke(KeyStroke keyStroke) {
        // do your key bindings

        // first get ActionMap and InputMap
        InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = getActionMap();

        // then the keystroke
        String key = keyStroke.toString();

        inputMap.put(keyStroke, key); // map the keystroke to a String key
        actionMap.put(key, exitAction); // map the same String key to the Action
    }

    // method that allows outside classes to swap views
    public void showCard(String key) {
        cardLayout.show(this, key);
    }
}

Upvotes: 4

Related Questions