Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: How to remove default KeyStrokes from any JComponent?

I want to control which keystroke belong to which Jcomponent. I even want to get how to remove the default keystrokes associated with a Jcomponent and replace them with other favourite keystrokes.

I followed this oracle tutorial, it gives an example with JButton, I tried it and it works fine, but when I try it with JComboBox it doesn’t work!

What exactly I tried is removing the SPACE key, that is prevent the JComponent from responding to SPACE presses

I used this code to remove the SPACE key:

firstButton.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "none");

The same thing for JComboBox

sizesComboBox.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "none");

But doesn’t work, that it is (JComboBox) still respond to the SPACE key

For the firstButton that I removed the effect of SPACE presses; I added the key F, So that now the firstButton is pressed when you press the F key on the keyboard, ant not respond to SPACE (intended). Note that presses of F take place even if firstButton doesn’t have the focus (JComponent.WHEN_IN_FOCUSED_WINDOW)

This is a SSCCE code showing my example:
Note: I intentionally didn’t add the line of code above to the second button "secondButton" so it is still respond to SPACE by default.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;

public class KeyStrokeTest extends JPanel
{

    JPanel widgetPanel;
    JPanel textAreaPanel;
    JButton firstButton;
    JButton secondButton;
    JTextArea textArea;
    JComboBox<Integer> sizesComboBox;

    public KeyStrokeTest()
    {
        firstButton = new JButton("First");
        firstButton.addActionListener(eventWatcher);
        firstButton.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "none");
        firstButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F"), "F Key");
        firstButton.getActionMap().put("F Key", eventWatcher);

        secondButton = new JButton("Second");
        secondButton.addActionListener(eventWatcher);

        sizesComboBox = new JComboBox<>();
        sizesComboBox.addItemListener(new itemListenerClass());
        for (int i = 1; i <= 8; i++)
        {
            sizesComboBox.addItem(i);
        }
        sizesComboBox.setSelectedIndex(0);
        sizesComboBox.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "none");


        textArea = new JTextArea(0, 0);
        JScrollPane scrollTextArea = new JScrollPane(textArea);
        scrollTextArea.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        textArea.setEditable(false);

        widgetPanel = new JPanel();
        textAreaPanel = new JPanel(new BorderLayout());

        widgetPanel.add(firstButton);
        widgetPanel.add(secondButton);
        widgetPanel.add(sizesComboBox);

        textAreaPanel.add(scrollTextArea, BorderLayout.CENTER);

        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, textAreaPanel, widgetPanel);
        splitPane.setDividerLocation(280);
        splitPane.setResizeWeight(.5d);
        this.setLayout(new BorderLayout());
        this.add(splitPane);
    }
    AbstractAction eventWatcher = new AbstractAction()
    {
        @Override
        public void actionPerformed(ActionEvent ae)
        {
            Object source = ae.getSource();
            if (source == firstButton)
            {
                textArea.append("First button clicked\n");
            }
            if (source == secondButton)
            {
                textArea.append("Second button clicked\n");
            }
        }
    };

    private class itemListenerClass implements ItemListener
    {

        @Override
        public void itemStateChanged(ItemEvent e)
        {
            if (e.getSource() == sizesComboBox)
            {
                if (textArea != null)
                {
                    textArea.append("Item " + sizesComboBox.getSelectedItem() + "\n");
                }
            }
        }
    }

    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame("KeyStroke Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(500, 300);
        frame.add(new KeyStrokeTest(), BorderLayout.CENTER);
        frame.setVisible(true);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                UIManager.put("swing.boldMetal", Boolean.FALSE);
                createAndShowGUI();
            }
        });
    }
}

The reason why I want to control over default keystrokes for JComponent is because I want to remove the default effect of SPACE on all the JComponent, except one button that will respond to the SPACE presses, wherever the focus is on, using JComponent.WHEN_IN_FOCUSED_WINDOW, so that clicking another component (and moving the focus away from the excepted button) will not prevent the effect of SPACE on that button.


Another point: If you tested the code above, you will notice that selecting an item from the JComboBox poduces two lines of, if you select item "4" the output in the JTextArea is

Item 4
Item 4

Why two??

Thanks.

like image 413
Saleh Feek Avatar asked Jan 27 '13 09:01

Saleh Feek


1 Answers

But doesn't work, that it is (JComboBox) still respond to the SPACE key

You should use the JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT InputMap like so (As you may notice I make use of KeyEvent and KeyStroke.getKeyStroke(int key,int modifier,boolean onRelease) as its more readable and less prone to mistakes i.e typing the wrong string argument etc.):

sizesComboBox.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
        .put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0,false), "none");

The reason for this as far as I see is explained nicely here:

The component contains (or is) the component that has the focus. This input map is commonly used for a composite component — a component whose implementation depends on child components. For example, JTables make all their bindings using WHEN_ANCESTOR_OF_FOCUSED_COMPONENT so that if the user is editing, the up-arrow key (for example) still changes the selected cell.

So I deduce JCombobox is a composite component and thus we need the correct InputMap - WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, to remove all its inner components KeyBinding functionality for a specific key i.e SPACE.

Another point: If you tested the code above, you will notice that selecting an item from the JComboBox poduces two lines of, if you select item "4" the output in the JTextArea is

Item 4
Item 4

Why two??

As said by @mKorbel (+1 to his comment) there are 2 events which can occur:

  • an item is deselected
  • an item is selected

These events, occur in pairs as when we select a new value the old one is deselected. Thus we must check for this and act appropriately:

@Override
public void itemStateChanged(ItemEvent e)
{
   if(e.getStateChange()==ItemEvent.SELECTED) { 
       //am item was selected do something
   }
}

Other suggestions:

  • Dont call setSize on JFrame.

  • Use an appropriate LayoutManager and/or override getPreferredSize to return Dimensions which fit the contents and call pack() on JFrame before setting is visible and after adding components.

like image 94
David Kroukamp Avatar answered Nov 04 '22 07:11

David Kroukamp