2

For a program, I was using a KeyListener to add something to an ArrayList when pressing the button '1'. Objects in this list are being visualised constantly. With the KeyListener, this worked fluently, even when keeping the button pressed.

Later, I added a JMenuBar to the GUI. Adding something to the ArrayList now has an own JMenuItem with its accelerator set to the KeyStroke '1' and an ActionListener which performs the same stuff than the KeyListener before. However, the performance now is very bad. Keeping '1' pressed is going to lag extremely, it's very slow compared to the KeyListener.

Why is it so slow? Am I doing something wrong? Is there a better way?

    ...
    AL al = new AL();
    menu.add(createMenuItem("Add", KeyEvent.VK_1, al));
}

private JMenuItem createMenuItem(String text, int key, ActionListener al){
    JMenuItem menuItem = new JMenuItem(text);
    menuItem.setAccelerator(KeyStroke.getKeyStroke(key, 0));
    menuItem.addActionListener(al);
    return menuItem;
}

private class AL implements ActionListener{
    public void actionPerformed(ActionEvent e){
        int keycode = ((JMenuItem)e.getSource()).getAccelerator().getKeyCode();
        bla(keycode);
    }
}
morrow
  • 174
  • 1
  • 15
  • What is your `keyPressed(keycode);` code supposed to do? – Hovercraft Full Of Eels Mar 08 '12 at 17:49
  • I didn't change this method at all, it works fine with the KeyListener. But to answer your question, e.g. it adds an object to the ArrayList if(keycode==KeyEvent.VK_1), or the program exits when ==KeyEvent.VK_ESCAPE. – morrow Mar 08 '12 at 17:53
  • 1
    does it do this by tripping the key listener? – Hovercraft Full Of Eels Mar 08 '12 at 18:03
  • No, it has nothing to do with the KeyListener, sorry for my confusing naming! I could also call it `bla(keycode);` – morrow Mar 08 '12 at 18:05
  • 4
    Come up with an SSCCE http://sscce.org – Guillaume Polet Mar 08 '12 at 18:12
  • 1
    Yes, I agree with the recommendation to come up with an SSCCE. Most of us have no problems with "delay" issues when using ActionListeners making the fact that you're using an ActionListener less likely to be the source of your problems and more likely that there is something else going on with your code. What? I have no idea, and the [sscce](http://sscce.org) can help with this. Please check the link before replying as there are a lot of misinterpretations on just what we're asking for that are cleared by the link. – Hovercraft Full Of Eels Mar 08 '12 at 19:00
  • I can't see how an SSCCE could help. The code is exactly the same! The only difference is the stuff mentioned, within a) KeyListener.keyTyped(), b) ActionListener.actionPerformed(). An SSCCE wouldn't give you any more information about my issue. However, I have a theory: When using an Accelerator+ActionListener, there also must be some kind of KeyListener. Instead of directly calling the code, this KeyListener however calls the ActionListener. Could this be the reason for my performance issues? Is there a way to circumvent it? – morrow Mar 08 '12 at 20:15
  • 2
    I don't know since I don't have code I can run, test, modify and check. I don't know how long you've been answering questions here, but if you do it for any length of time, you'll rapidly change your opinion on how valuable an sscce is. It's kind of up to you and depends on how much you really need our help. – Hovercraft Full Of Eels Mar 08 '12 at 20:19
  • I'm sure it can be valuable, I merely can exclude an error within the rest of the code (otherwise I'd have to have problems with the KeyListener as well). Let me rephrase my question: Is there a way to directly access this 'KeyListener' used from the JMenuItem accelerator and changing it to call my method instead of the ActionListener defined for the JMenuItem? If you can't answer this question, I will come up with an SSCCE. Thanks for your help by the way ;) – morrow Mar 08 '12 at 20:29

2 Answers2

5

It looks like the slowdown is how the menu accelerators are handled. It might be L&F or even OS since when I profile it, there is no hotspot in the Java code (WindowsXP) dependent. A workaround could be to add the key binding to the root pane instead of using an menu accelerator.

Press '1' to trigger KeyListener on button (fast) Press '2' to trigger menu accelerator (slow) Press '3' to trigger KeyBinding on button (fast) Press '4' to trigger KeyBinding on root pane (fast)

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;

public class TestKeySpeed {
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                final JTextArea area = new JTextArea(20, 40);
                area.setEditable(false);

                JButton button = new JButton("Just something that has focus");
                button.addKeyListener(new KeyAdapter() {
                    @Override
                    public void keyPressed(KeyEvent e) {
                        if (e.getKeyCode() == KeyEvent.VK_1) {
                            area.append("1");
                        }
                    }
                });

                AbstractAction action = new AbstractAction("Add") {
                    {
                        putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke('2'));
                    }

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        area.append("2");
                    }
                };
                button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
                        KeyStroke.getKeyStroke('3'), "add3");
                button.getActionMap().put("add3", action);

                JMenu menu = new JMenu("File");
                menu.add(action);
                JMenuBar bar = new JMenuBar();
                bar.add(menu);
                JFrame frame = new JFrame("Test");
                frame.getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
                        KeyStroke.getKeyStroke('4'), "add4");
                frame.getRootPane().getActionMap().put("add4", action);

                frame.setJMenuBar(bar);
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.getContentPane().add(new JScrollPane(area));
                frame.getContentPane().add(button, BorderLayout.PAGE_END);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                button.requestFocusInWindow();
            }
        });
    }
}
Walter Laan
  • 2,956
  • 17
  • 15
  • Wow, this is exactly what I was looking for, thanks very much for your answer. Your SSCCE is even much better than what I would have tried to do ;) So I got my stuff working as I intended by using menu accelerators for the menus as well as KeyBindings on the JPanel (same Actions for both though). – morrow Mar 09 '12 at 16:36
  • '2' is also slow on Win7. Obviously, it is OS dependent. – morrow Mar 09 '12 at 20:52
  • @Morrow: Glad you've got a solution (Walter is one smart dude, 1+), but consider -- *he* had to create an SSCCE to come up with and test a solution, and so you see how the SSCCE is necessary for providing a solution, whether made by you or by him. If you had done this first, you would have saved him a **lot** of extra work that he really didn't need to waste time on, and you would have gotten a solution a lot sooner. The crux here is that it is *your* problem, and so the onus of putting forth the effort to make it easier for us to be able to help you should be on you, not on Walter. – Hovercraft Full Of Eels Mar 09 '12 at 22:27
  • Sorry to beat on a dead horse, but as you can tell, it is one that I feel very strongly about. I am a proselytizer for the religion of SSCCE. Again I'm happy for you, and glad to have learned yet another new thing from Walter. – Hovercraft Full Of Eels Mar 09 '12 at 22:29
  • I am aware of that and I apologise, it really wasn't my intention to shift the work on to somebody else! When I posted the problem, I actually thought this issue was something that 'pros' here would know about and that somebody could tell me easily what has to be done. Therefore, I didn't come up with an SSCCE in the first place. – morrow Mar 10 '12 at 04:41
4

Something else is slowing your application. This example remains responsive with over a dozen Key Bindings. One useful approach is to let menu items and other components share the same actions, as shown here and here.

Addendum: Instead of implementing ActionListener, implement Action by extending AbstractAction, which will make it easier to manage the accelerator key.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Hm, my problem is not unresponsiveness due to several key bindings but due to replacing a (perfectly working) KeyListener with the JMenuItem accelerator+ActionListener. I will post an SSCCE tomorrow, I hope it'll help. Thanks so far! – morrow Mar 08 '12 at 22:38
  • @Morrow: Sorry for the confusion; menus use key bindings internally to implement accelerators. Address me in a comment when your [sscce](http://sscce.org/) is extant. – trashgod Mar 09 '12 at 01:08
  • I'm sorry, I didn't get your answer yesterday, even though it would have been the solution I was looking for. Walter Laan has already provided a nice example. Thanks anyway! – morrow Mar 09 '12 at 16:39