3

I want to trigger an save action anywhere in my application (Control+S). I've added the necessary key binding, and the action triggers as expected. However, if I try Control+S on a JTable the table starts my custom action and activates the table cell for editing. I think I've disabled the edit action in the table's input map. What am I missing here?

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.UIManager;

public class TestTableKeyBinding extends JFrame{

JTable table;
JScrollPane scroll;

public static void main(String[] args){
    TestTableKeyBinding test = new TestTableKeyBinding();
    test.setVisible(true);
}

TestTableKeyBinding(){
    super();
    initUI();
    addKeyBindings();
}

void initUI(){
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    String[] headers = new String[]{"apples", "bananas"};
    String[][] data = new String[][]{{"1", "2"},{"4","6"}};
    table = new JTable(data, headers);
    table.setCellSelectionEnabled(true);
    scroll = new JScrollPane();
    scroll.setViewportView(table);
    this.add(scroll);
    this.pack();
    this.setSize(new Dimension(300, 400));  

}

void addKeyBindings(){
    //root maps
    InputMap im = this.getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    ActionMap am = this.getRootPane().getActionMap();

    //add custom action
    im.put(KeyStroke.getKeyStroke("control S"), "save");
    am.put("save", saveAction());

    //disable table actions via 'none'
    table.getInputMap().put(KeyStroke.getKeyStroke("control S"), "none");
    table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("control S"), "none");
    table.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke("control S"), "none");
    table.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control S"), "none");
    ((InputMap)UIManager.get("Table.ancestorInputMap")).put(KeyStroke.getKeyStroke("control S"), "none");
}

AbstractAction saveAction(){
    AbstractAction save = new AbstractAction(){
        public void actionPerformed(ActionEvent e) {
            // TODO Auto-generated method stub
               JOptionPane.showMessageDialog(TestTableKeyBinding.this.table, "Action Triggered.");
        }           
    };
        return save;
    }   
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
kirbs
  • 168
  • 1
  • 8

3 Answers3

4

Like @Guillaume, I had no problem running your code. You might look for a CellEditor that inadvertently defeats editingStopped(), discussed here. An sscce may help clarify the problem.

Addendum: You can cancel editing in the saveAction() handler, as shown below.

table.editingCanceled(null);

For reference, I've updated your example in several respects:

  • Control-S is not bound to any JTable Action in common Look & Feel implementations, so there's no need to remove it.
  • For cross-platform convenience, use getMenuShortcutKeyMask().
  • Swing GUI objects should be constructed and manipulated only on the event dispatch thread.

Code:

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;

public class TestTableKeyBinding extends JFrame {

    private static final int MASK =
        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    private JTable table;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                TestTableKeyBinding test = new TestTableKeyBinding();
                test.setVisible(true);
            }
        });
    }

    TestTableKeyBinding() {
        super();
        initUI();
        addKeyBindings();
    }

    private void initUI() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        String[] headers = new String[]{"apples", "bananas"};
        String[][] data = new String[][]{{"1", "2"}, {"4", "6"}};
        table = new JTable(data, headers);
        table.setCellSelectionEnabled(true);
        this.add(new JScrollPane(table));
        this.pack();
        this.setSize(new Dimension(300, 400));

    }

    private void addKeyBindings() {
        //root maps
        InputMap im = this.getRootPane().getInputMap(
            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        ActionMap am = this.getRootPane().getActionMap();
        //add custom action
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, MASK), "save");
        am.put("save", saveAction());
    }

    private AbstractAction saveAction() {
        AbstractAction save = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                    TestTableKeyBinding.this.table, "Action Triggered.");
                table.editingCanceled(null);
            }
        };
        return save;
    }
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
2

I finally figured it out. There is no key binding to set, the action map doesn't exist. So the calls to put weren't doing anything.

table.getInputMap().put(KeyStroke.getKeyStroke("control S"), "none");
table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("control S"), "none");
table.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke("control S"), "none");
table.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control S"), "none");
((InputMap)UIManager.get("Table.ancestorInputMap")).put(KeyStroke.getKeyStroke("control S"), "none");

JTable passes the key event to editCellAt there by triggering the table to enable editing. There are two ways around this. 1) Add an action reference to the JTable's input/action maps or 2) override editCellAt to return false. Here's how I got around the issue.

public boolean editCellAt(int row, int column, EventObject e){              
            if(e instanceof KeyEvent){
                int i = ((KeyEvent) e).getModifiers();
                String s = KeyEvent.getModifiersExText(((KeyEvent) e).getModifiers());
                //any time Control is used, disable cell editing            
                if(i == InputEvent.CTRL_MASK){
                    return false;
                }
            }               
            return super.editCellAt(row, column, e);                
        }
kirbs
  • 168
  • 1
  • 8
  • Based on a clearer understanding of your question, I've shown another approach [here](http://stackoverflow.com/a/10575771/230513). – trashgod May 14 '12 at 04:28
0

I just had a similar case, where I wanted to reassign the left/right keys. What helped me was look at https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html

Finally I came up with code like this:

    {
        InputMap im = jTable1.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "decStash");
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "incStash");
    }
    {
        InputMap im = jTable1.getInputMap(JComponent.WHEN_FOCUSED);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "decStash");
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "incStash");
    }
    {
        ActionMap am = jTable1.getActionMap();
        am.put("decStash", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                log.debug("decStash");
            }
        });
        am.put("incStash", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                log.debug("incStash");
            }
        });
    }

Note that my code does not lookup the root component as the selected answer does.

Queeg
  • 7,748
  • 1
  • 16
  • 42