3

We have a legacy piece of software that runs on Java 1.6. When we finally got the green light to upgrade it to Java 1.8, the following problem manifested itself.

We have a set of radio buttons with accelerator keys. If a JTextComponent of any sort has the focus, and you hit one of the radio button accelerators (say, ALT-s), and you release the "s" before you release the ALT, the UIManager will activate the menu bar. (This only happens with the Windows look and feel)

Looks like a bug, and I've been thinking of writing a workaround by "consuming" the ALT release in those cases, but maybe someone has a better idea? Using a different look and feel is not an option, nor is switching off the standard Alt behavior in the UI Manager.

Here's a short code sample. Note there are no accelerator/mnemonic conflicts of any sort.

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

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.UIManager;

public class MnemonicTest extends JFrame {  

public MnemonicTest() {
    super("MnemonicTest");
    init();
}

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

private void init() {
    try {
         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch (Exception e1) {
        e1.printStackTrace();
    }

    setSize(new Dimension(500,400));
    JButton stopButton = new JButton("Stop");
    stopButton.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e) {        
            System.exit(0);         
        }});

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

    this.getContentPane().add(stopButton, BorderLayout.SOUTH);

    JMenuBar jMenuBar = new JMenuBar(); 
    JMenu menu = new JMenu("XXX");  

    JMenuItem a1 = new JMenuItem("a1", 'A');
    JMenuItem b1 = new JMenuItem("b1", 'B');
    JMenuItem c1 = new JMenuItem("c1", 'C');

    menu.add(a1);
    menu.add(b1);
    menu.add(c1);

    jMenuBar.add(menu);
    this.setJMenuBar(jMenuBar);

    JPanel p = new JPanel();
    ButtonGroup group = new ButtonGroup();
    p.add(new JTextField("XXXXXXXXXX"), BorderLayout.CENTER);
    JRadioButton but1 = new JRadioButton("test");
    but1.setMnemonic('s');
    JRadioButton but2 = new JRadioButton("2222");
    p.add(but1);
    p.add(but2);
    group.add(but1);
    group.add(but2);

    getContentPane().add(p, BorderLayout.CENTER);
}
}
Mad Max
  • 51
  • 4

1 Answers1

0

I did manage to find a solution that works, even if it's not exactly a beauty contest winner. If you have a better one, please post it!!

The problem seems to be that the KeyEvent is sent to the radio button and not to the pane or the text field. And when the system sees that the ALT key has been released, it invokes the default action.

Of course, a plain vanilla Alt when a radio button has the focus should still do what it is supposed to do: activate the menu bar.

If you press, say, Alt-S (our accelerator), the radio button will receive: keyPressed(Alt) -> keyPressed("S") -> keyReleased("S") -> keyReleased(Alt).

Thus, if we save the value of the last key pressed, we'll consume the last event (keyReleased(Alt)) unless the last key pressed was also an Alt.

This is a workaround, and not a pretty one, but it works. The code is as follows (I've left my debug statements in the code):

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

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.UIManager;

public class MnemonicTest extends JFrame {  
    int codeLast = 0;

    private final class RadioButtonKeyAdapter extends KeyAdapter {
        private static final int NO_CODE = 0;
        private int lastCode = 0;

        @Override
        public void keyPressed(KeyEvent e) {
            System.out.println("pressed source: " + e.getSource() + "\n" + e.getKeyCode());
            this.setLastCode(e.getKeyCode());
        }

        @Override
        public void keyReleased(KeyEvent e) {
            System.out.println("released source: " + e.getSource() + "\n" + e.getKeyCode());
            if (e.getKeyCode() == KeyEvent.VK_ALT && this.getLastCode() != e.getKeyCode()) {
                e.consume();
            }

            this.setLastCode(NO_CODE);
        }

        private int getLastCode() {
            return lastCode;
        }

        private void setLastCode(int lastCode) {
            this.lastCode = lastCode;
        }
    }

    private static final long serialVersionUID = 1L;

    public MnemonicTest() {
        super("MnemonicTest");
        init();
    }

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

    private void init() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        setSize(new Dimension(500,400));
        JButton stopButton = new JButton("Stop");
        stopButton.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e) {        
                System.exit(0);         
            }});

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

        this.getContentPane().add(stopButton, BorderLayout.SOUTH);

        JMenuBar jMenuBar = new JMenuBar(); 
        JMenu menu = new JMenu("XXX");  

        JMenuItem a1 = new JMenuItem("a1", 'A');
        JMenuItem b1 = new JMenuItem("b1", 'B');
        JMenuItem c1 = new JMenuItem("c1", 'C');

        menu.add(a1);
        menu.add(b1);
        menu.add(c1);

        jMenuBar.add(menu);
        this.setJMenuBar(jMenuBar);

        JPanel p = new JPanel();
        ButtonGroup group = new ButtonGroup();
        JTextField textField = new JTextField("XXXXXXXXXX");
        p.add(textField, BorderLayout.CENTER);
        JRadioButton but1 = new JRadioButton("test");
        but1.setMnemonic('s');
        JRadioButton but2 = new JRadioButton("2222");
        p.add(but1);
        p.add(but2);
        group.add(but1);
        group.add(but2);

        getContentPane().add(p, BorderLayout.CENTER);

        but1.addKeyListener(new RadioButtonKeyAdapter());
        but2.addKeyListener(new RadioButtonKeyAdapter());
    }
}
Mad Max
  • 51
  • 4