Tuesday, May 22, 2007

Linux EventQueue Problem

As a part of an ongoing project I had to write a little on-screen keyboard. It is a very simple thing: Just a panel consisting of a series of JButtons that - when clicked - insert KeyEvents into the EventQueue to enable any Swing text component to be used. I do not have the original code at hand right now, but I reconstructed it at home:

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

import javax.swing.*;

public class TestTool extends JFrame {

    private JTextArea textArea;

    private JButton btnA;

    private JButton btnB;

    private KeyEvent createEvent(Component aSrc, char aChar) {
        return new KeyEvent(aSrc, KeyEvent.KEY_TYPED, System
                .currentTimeMillis(), 0, 0, aChar);
    }

    ActionListener btnLstn = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue();
            if (e.getSource() == btnA) {
                q.postEvent(createEvent(btnA, 'A'));
            } else if (e.getSource() == btnB) {
                q.postEvent(createEvent(btnB, 'B'));
            }
        }
    };

    public static void main(String[] args) {
        new TestTool().go();
    }

    protected void go() {
        Container tContent = getContentPane();
        tContent.setLayout(new BorderLayout());
        textArea = new JTextArea();
        tContent.add(textArea);
        btnA = new JButton("A");
        btnB = new JButton("B");
        btnA.setFocusable(false);
        btnB.setFocusable(false);
        btnA.addActionListener(btnLstn);
        btnB.addActionListener(btnLstn);
        JPanel pnl = new JPanel(new FlowLayout());
        pnl.add(btnA);
        pnl.add(btnB);
        tContent.add(pnl, BorderLayout.SOUTH);
        setSize(500, 380);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
}

Starting this will bring up a little window with two buttons "A" and "B". Clicking either of these will insert the appropriate letter into the text area. I tested this on Ubuntu Feisty Fawn with no problems at all. However on RH9 this (well, not exactly this, but very similar) code does not work reliably (that code works on Windows however, so it cannot be completely broken). On Red Hat 9 the button clicks just do not produce letters in the text area. It can be made to work, when I insert a Thread.sleep(1500) before putting the new KeyEvent on the queue. This will make the button go "down", remain there for 1,5s and then flip out again, usually (but not always) producing a letter in the text area. At the moment I have no idea what causes this strange behavior. On Thursday I will compare the code above with the original and try it out and Red Hat...

Any ideas are of course very welcome :)

4 comments:

technomage said...

Hard to comment without seeing how you actually send the event to the text component. Are you posting to the event queue or sending directly to Component.process[Key]Event()?

Vity said...

you should use this in the main method:

SwingUtilies.invokeLater(new Runnable() {
public void run() {
new TestTool.go();
}
})

Anonymous said...

what version of java do you use?
Have you tried another Linux

Anonymous said...

For each key press, create a new KeyEvent and put it on the queue using Component.dispatch(AWTEvent). Do this from the event dispatch thread (EDT). If you are doing it in the button's action listner, then you are already running in the EDT. Otherwise, wrap your code in a Runnable and invoke it with SwingUtilities.invokeLater(Runnable) like the previous commenter suggested. ALWAYS, interact with swing components in the EDT.