Search code examples
javaswingkeystroke

Why does one Swing keystroke work but not the other?


I am writing a Swing program that has a JTable; I want to use control-V to paste into the JTable, and control-S to save the information that's on the JTable.

I first used JTable.registerKeyboardAction() to register the action with the control-V key, and that works, but I noticed that its javadoc says it is obsolete and that new code should use the input map and action map for this purpose.

I had used those for the control-S key, which I had also mapped to a JButton, so I figured it would be easy to duplicate. Here's the code snippet I have now for creating the JFrame:

  private JFrame createMainframe()
  {
    JFrame frame = new JFrame("VisaExtraction");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    String saveFileActionName = "Save";
    String pasteActionName = "Paste";

    Action saveFileAction = new SaveFileAction(saveFileActionName, frame, tableModel);
    Action pasteAction    = new PasteAction(pasteActionName, frame, tableModel);

//    JButton saveButton = new JButton(saveFileAction);
//    saveButton.setMnemonic(KeyEvent.VK_S);

    JPanel topPanel = new JPanel();
//    topPanel.add(saveButton);

    mainTable = new LastColumnChangesWidthJTable(tableModel);
    JScrollPane scrollPane = new JScrollPane(mainTable);

    // set ctrl-s to the 'saveFile' action
    //  and ctrl-v to the 'paste' action
    InputMap  tableInputMap  = mainTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);

    KeyStroke saveKeystroke  = KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK);
    tableInputMap.put(saveKeystroke, saveFileActionName);

    KeyStroke pasteKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK);
    tableInputMap.put(pasteKeystroke, pasteActionName);

    // set the saveFile and paste actions to be executed when invoked.
    ActionMap tableActionMap = mainTable.getActionMap();
    tableActionMap.put(saveFileActionName,  saveFileAction);
    tableActionMap.put(pasteActionName,     pasteAction);

    frame.add(topPanel,   BorderLayout.NORTH);
    frame.add(scrollPane, BorderLayout.CENTER);

    frame.pack();

    // register ctrl-v to paste into the JTable
//    mainTable.registerKeyboardAction
//    ( actionListener -> handlePaste(tableModel, new VisaExtractionListener(tableModel)),
//      KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK), 
//      JComponent.WHEN_IN_FOCUSED_WINDOW
//    );

    return frame;
  }

As you can see, I'm doing the same things with "S" and "save" that I'm doing with "V" and "paste", but after I start the program, "control-S" works (to the extent of telling me there's nothing to save) and "control-V" does not (the breakpoint in the actionPerformed() method is not hit).

What could be causing the difference between these two?


Solution

  • Your problem is likely due to the JTable already using ctrl-V action in another input map. Understand that components have 3 input maps, and I believe that the one for JComponent.WHEN_FOCUSED has precedence over WHEN_IN_FOCUSED_WINDOW.

    My MCVE for proof of concept. Change the commented field in the code below to see which works:

    import java.awt.event.ActionEvent;
    import java.awt.event.InputEvent;
    import java.awt.event.KeyEvent;
    
    import javax.swing.*;
    
    public class Foo {
        public static void main(String[] args) {
            Integer[][] rowData = {{1, 2}, {3, 4}};
            String[] columnNames = {"A", "B"};
            JTable table = new JTable(rowData, columnNames);
            JScrollPane scrollPane = new JScrollPane(table);
    
            // int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
            int condition = JComponent.WHEN_FOCUSED;
            InputMap inputMap = table.getInputMap(condition);
            ActionMap actionMap = table.getActionMap();
    
            KeyStroke saveKeystroke  = KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK);
            KeyStroke pasteKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK);
    
            inputMap.put(saveKeystroke, saveKeystroke.toString());
            inputMap.put(pasteKeystroke, pasteKeystroke.toString());
    
            actionMap.put(saveKeystroke.toString(), new AbstractAction() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println("save action");
                }
            });
            actionMap.put(pasteKeystroke.toString(), new AbstractAction() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println("paste action");
                }
            });
    
            JFrame frame = new JFrame("Foo");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(scrollPane);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    }
    

    Again in the future, the effort of creating the MCVE should be yours since you are the one asking for help.

    Edit: I was wrong. The input map that was already in use and was messing you up was the JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT map. This had a non-null key String for the control-V key in my MCVE update:

    import java.awt.event.ActionEvent;
    import java.awt.event.InputEvent;
    import java.awt.event.KeyEvent;
    import javax.swing.*;
    
    public class Foo {
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> createGui());
        }
    
        public static void createGui() {
            Integer[][] rowData = { { 1, 2 }, { 3, 4 } };
            String[] columnNames = { "A", "B" };
            JTable table = new JTable(rowData, columnNames);
            JScrollPane scrollPane = new JScrollPane(table);
    
            int condition = JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT;
            // int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
            // int condition = JComponent.WHEN_FOCUSED;
            InputMap inputMap = table.getInputMap(condition);
            ActionMap actionMap = table.getActionMap();
    
            KeyStroke saveKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK);
            KeyStroke pasteKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK);
    
            String pasteKey = (String) inputMap.get(pasteKeystroke);
            System.out.println(pasteKey);
    
            inputMap.put(saveKeystroke, saveKeystroke.toString());
            inputMap.put(pasteKeystroke, pasteKeystroke.toString());
    
            actionMap.put(saveKeystroke.toString(), new MyAction("Save Action"));
            actionMap.put(pasteKeystroke.toString(), new MyAction("Paste Action"));
    
            JFrame frame = new JFrame("Foo");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(scrollPane);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    }
    
    @SuppressWarnings("serial")
    class MyAction extends AbstractAction {
        private String text;
    
        public MyAction(String text) {
            this.text = text;
        }
    
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println(text);
        }
    }