2

I have an older stand alone java swing-based app that uses JFrame with JMenuBar containing multiple Jmenu elements (with respective JMenuItem items).

After upgrading to the latest 1.6.0_41 (or 1.7.x) JVM on Windows (7 and vista) I've noticed that the menu item with the shortcut Ctrl-C (or Ctrl-Insert) doesn't receive its ActionEvent anymore if JTable is added to the frame. The menu ActionListener is invoked if the menu is accessed by the mouse click however. The shortcut works if JTable is removed. If I change the shortcut combination to something other than Ctrl-C or Ctrl-Insert (i.e. Ctrl-L) the ActionListener is invoked.

The way it used to work (I've just confirmed it with jvm 1.4, on Windows Vista - I know it's been awhile since that environment got any serious attention :) is that Ctrl-C will perform the standard copy to clipboard function inside of the JTable if the focus was inside of an editable field. Otherwise my menu ActionListener was invoked via shortcut assigned through setAccelerator() method.

It looks like JTable implementation changed in 1.6.* to process Ctrl-C bound event differently on Windows.

Running this app on Mac OS (JVM 1.6.0_43) I can see ActionListener is invoked via Ctrl-C shortcut. Although it might be because the JTable uses Command-C instead of Ctrl-C to copy to the clipboard under Mac OS.

I've extracted the relevant portion of the code that demonstrates the problem. Any suggestions are greatly appreciated.

public class TestFrame extends JFrame {

public TestFrame(String title) {

    super(title);
}

private void init() {

    getContentPane().setLayout(new BorderLayout());

    addMenu();
    addTable();

    // Change default exit operation
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    pack();
    setVisible(true);
}

private void addTable() {

    JTable jTable = new JTable(createTableModel());

    // Place table in JScrollPane
    JScrollPane scrollPane = new JScrollPane(jTable);

    // Add Table
    add(scrollPane, BorderLayout.CENTER);
}

private TableModel createTableModel() {

    Object[][] data = new Object[][]{ 
            {new Date(), "First Row, 2nd column", "First Row, 3rd column"},
            {new Date(), "Second Row, 2nd column", "Second Row, 3rd column"},
        };

    Object[] columnNames = new Object[]{"Date", "Type", "Description"};

    DefaultTableModel model = new DefaultTableModel(data, columnNames) {

        public boolean isCellEditable(int row, int column) {
            return column != 0;
        }

    };

    return model;
}

private void addMenu() {

    // Create the menu bar.
    JMenuBar menuBar = new JMenuBar();
    setJMenuBar(menuBar);

    JMenu editMenu = new JMenu("Edit");
    menuBar.add(editMenu);

    TestActionListener listener = new TestActionListener();
    JMenuItem menuItem = null;

    menuItem = new JMenuItem("Copy 1");
    menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, ActionEvent.CTRL_MASK));
    menuItem.addActionListener(listener);
    editMenu.add(menuItem);

    menuItem = new JMenuItem("Copy 2");
    menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
    menuItem.addActionListener(listener);
    editMenu.add(menuItem);

    menuItem = new JMenuItem("Copy 3");
    menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, ActionEvent.CTRL_MASK));
    menuItem.addActionListener(listener);
    editMenu.add(menuItem);
}


public static void main(String[] args) {

    TestFrame frame = new TestFrame("Test");
    frame.init();
}


private static class TestActionListener implements ActionListener {

    public void actionPerformed(ActionEvent e) {
        System.out.println("TestFrame.TestActionListener.actionPerformed(): e="+ e);
    }
}

}

pavel
  • 2,964
  • 2
  • 14
  • 14

2 Answers2

2

The problem is that your frame is not focused and there are no elements in your whole component hierarchy which has the focus, meaning that no one will "grab" the event and try to do something with it. Since JMenuItem's bind their shortcut to the input map JComponent.WHEN_IN_FOCUSED_WINDOW, your shortcut never "answers" the event.

To fix this, either put the focus on one of the component or directly on the JFrame (for example with frame.requestFocusInWindow();). Small example here:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

public class TestFrame extends JFrame {

    public TestFrame(String title) {

        super(title);
    }

    private void init() {

        getContentPane().setLayout(new BorderLayout());

        addMenu();
        addTable();

        // Change default exit operation
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        pack();
        setVisible(true);
    }

    private void addTable() {

        JTable jTable = new JTable();

        // Place table in JScrollPane
        JScrollPane scrollPane = new JScrollPane(jTable);

        // Add Table
        add(scrollPane, BorderLayout.CENTER);
    }

    private void addMenu() {

        // Create the menu bar.
        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        JMenu editMenu = new JMenu("Edit");
        menuBar.add(editMenu);

        TestActionListener listener = new TestActionListener();
        JMenuItem menuItem = null;

        menuItem = new JMenuItem("Copy 1");
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.CTRL_MASK));
        menuItem.addActionListener(listener);
        editMenu.add(menuItem);

        menuItem = new JMenuItem("Copy 2");
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK));
        menuItem.addActionListener(listener);
        editMenu.add(menuItem);

        menuItem = new JMenuItem("Copy 3");
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_MASK));
        menuItem.addActionListener(listener);
        editMenu.add(menuItem);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                TestFrame frame = new TestFrame("Test");
                frame.init();
                frame.requestFocusInWindow();
            }
        });
    }

    private static class TestActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("TestFrame.TestActionListener.actionPerformed(): e=" + e);
        }
    }
}

Additional remarks:

  • Don't extend JFrame if not needed
  • Start your UI from the Event Dispatching Thread (EDT) by using SwingUtilities.invokeLater()
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • I have an idea why only Ctrl-C / Ctrl-Insert causes the problem. Sometime after version 1.4.x JTable was enhanced to copy the content of the selected table row into the clipboard. And that action is bound to Ctrl-C / Ctrl-Insert which intercepts my key binding intended for the action defined by the JMenu. It explains why other key combinations work but not Ctrl-C. If table is not empty then focusing the frame doesn't help to solve the problem. I've updated the original code to include a test data model to demonstrate that issue still remains. – pavel Apr 23 '13 at 01:02
1

If you question is how to remove the Control+C binding from the table then you can do:

KeyStroke copy = KeyStroke.getKeyStroke("control C");
InputMap im = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
im.getParent().remove(copy);

However, this will remove the binding from all tables.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • This makes total sense. I think the issue was with added JTable's enhanced capability to copy the content of the selected table row into the clipboard. And that action is bound to Ctrl-C / Ctrl-Insert which intercepts my key binding intended for the action defined by the JMenu. It explains why other key combinations work but not Ctrl-C. Thank you for this solution which disables the row content copy but still allows for the editable cell content to be copied to the clipboard which is exactly how it worked for me before. – pavel Apr 23 '13 at 01:32
  • Is there a way for both actions to be executed? Could JTable's action listener receive the Ctrl-C event and my JMenu also? My custom Jmenu action will copy the underlying model row but it wouldn't interfere with the text row content being copied to the clipboard. And I can see how having the row content in the clipboard could be helpful sometimes. – pavel Apr 23 '13 at 01:37
  • Events are only dispatched to a single component. I don't know how to dispatch it to multiple components. – camickr Apr 23 '13 at 04:11