Click to See Complete Forum and Search --> : KeyListener doesn't work on Windows OS


gamblor01
April 7th, 2010, 11:42 AM
I have a class that implements KeyListener like so:


public class ErrorChecker extends JFrame implements ActionListener, KeyListener


Inside of the constructor I made sure to add listeners for all of the components in the GUI, so that the keyboard shortcuts work no matter which component is in focus:


// add key listener to the textarea and all buttons (except quit)
textArea.addKeyListener(this);
openButton.addKeyListener(this);
clearButton.addKeyListener(this);
verboseButton.addKeyListener(this);



I have implemented the actionPerformed() and keyTyped() methods and they work exactly how I expect them to...at least on Linux. Then I had my friend checkout my latest code from SVN on his Windows box and none of the keyboard shortcuts work! I fired up the program in the eclipse debugger and set a breakpoint inside of keyTyped() -- no matter what you press or what is in focus, the breakpoint is never hit -- meaning Windows doesn't even seem to send the event to my Java program.

So in summary:

1. On Linux:
- actionPerformed() works perfectly
- keyTyped() works perfectly


2. On Windows:
- actionPerformed() works perfectly (i.e. listens for buttons to be clicked)
- keyTyped() seems to be ignored entirely


Here is the keyTyped() method:


public void keyTyped(KeyEvent e)
{
// Only rely on the key char if the event is a key typed event.
if (e.getID() == KeyEvent.KEY_TYPED)
{
// get the key typed
int c = e.getKeyChar();

// check for modifiers
int modifiersEx = e.getModifiersEx();
String modCode = KeyEvent.getModifiersExText(modifiersEx);



/* Not sure why but Ctrl with some keys produces some weird codes.
* Here is a list of some codes we are looking for (some are expected
* such as 127 for 'Del' while Ctrl-O is funky):
*
* Ctrl-O = 15
* Delete = 127
*/
if (c == '-' && modCode.equals("Ctrl"))
decreaseFont();
else if (c == '=' && modCode.equals("Ctrl"))
increaseFont();
else if (c == 15 && modCode.equals("Ctrl"))
actionPerformed(new ActionEvent(openButton, 0, ""));
else if (c == 127 && modCode.equals("Shift"))
textArea.setText("");
}
}



Any ideas why keyTyped() is flat out useless on Windows yet works perfectly on Linux? The JRE version doesn't seem to matter because we have different version of Sun Java 1.6 installed (and both fail). I also have IBM Java 1.5 installed on a Windows VM and key presses are also ignored on this system. I haven't tried on OS X yet, but I can give it a shot when I get home tonight.

Thanks!

keang
April 7th, 2010, 01:44 PM
Any ideas why keyTyped() is flat out useless on Windows The good news is it isn't useless on Windows (it works on my Windows system) which of course means the bad news is the problem is somewhere else.

Did one of the components with a key event listener definitely have focus when a key was pressed?

Can you produce a simple GUI with a single input field that works on Linux but not on Windows and post the code so we can see what you doing.

Martin O
April 7th, 2010, 02:06 PM
Any ideas why keyTyped() is flat out useless on Windows yet works perfectly on Linux?


If the problem is really as straightforward as that then you should be able to create and post an SSCCE (http://sscce.org/) that shows this.

gamblor01
April 7th, 2010, 02:25 PM
Actually...I just did some further testing and updated our GUI so that it just did a println statement in keyPressed() and keyReleased() ... it responded to both events! So then I added in a println statement into keyTyped() and it printed too but only when pressing a single key.

At this point it appears that the GUI on Windows has problems responding to key events when the CTRL key is being used. For example, in my code the combination Shift-Del will clear out the contents on the text area. If you type this, then the contents will be cleared. But try pressing Ctrl with any of the other 3 valid combinations and it won't respond. Holding Ctrl seems to cause something funky to happen and Ctrl-O, Ctrl--, and Ctrl-= all seem to be ignored.


EDIT: It looks like using the left Ctrl on my keyboard works fine with the O key (i.e. left Ctrl-O will open the JChooser dialog). It doesn't work with my right Ctrl key because I'm running this Windows VM inside of VirtualBox (and vbox uses right Ctrl as my "host" key...so it intercepts all uses of this key for its own purposes).

However, left Ctrl-- and left Ctrl-= still seem to be ignored entirely (no println message occurs when I press them). If you modify the code below and put in a println statement inside of keyPresed() and keyReleased(), you will see that they respond to the Ctrl-- and Ctrl-= keystrokes. For some reason keyTyped() still does not. Am I going to have to write code that works one way on Windows but another way on Linux??? That's not very "write once, run anywhere."

EDIT AGAIN:
Here is something super crazy...Windows seems to recognize all sorts of Ctrl sequences (hold Ctrl and type almost any key). Yet when I use the - or = keys...it just doesn't seem to work. In fact, it seems that nothing on the top row of keys (from tilde over to =, which means NONE of the numeric keys) are recognized in Windows. It also fails to recognize holding Ctrl and pressing these keys:

; (semicolon)
' (apostrophe or single quote)
, (comma)
. (period)
/ (forward slash -- though backslash works fine)


Alt works fine though...with ALL of the above keys. So maybe I'll just change the shortcut to Alt instead. I don't understand why Ctrl doesn't work though. I must be missing something but I do not know what it is.



Here is a super stripped down version of the code that should compile as-is:


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;


public class ErrorChecker extends JFrame implements ActionListener, KeyListener
{
private static final long serialVersionUID = 1L;

// user defined options
static boolean DEBUG = false;
static boolean VERBOSE = false;

// buttons to have on the GUI
private JButton openButton;
private JButton clearButton;
private JButton exitButton;
private JButton verboseButton;

// text area stuff
private JTextArea textArea;
private JScrollPane areaScrollPane;
private JPanel cen;

// font sizes
private int fontSize = 12;
private static final int MIN_FONT = 8;
private static final int MAX_FONT = 36;

// test for GUI not responding to keyTyped()
private static long num = 1;


public ErrorChecker()
{
super("ITM 6.x log parser");

setSize (800,600);
setLocation (100,100);

setDefaultCloseOperation(EXIT_ON_CLOSE);

setupGUI(fontSize);

setVisible(true);

}


private void setupGUI(int fontSize)
{
// setup everything inside of the JFrame
Container c = getContentPane();
cen = new JPanel();
cen.setLayout(new BorderLayout());

// define the JTextArea
textArea = new JTextArea();
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setFont(new Font("monospaced", Font.PLAIN, fontSize));

// embed in scroll pane to make the text area scrollable
areaScrollPane = new JScrollPane(textArea);
areaScrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
areaScrollPane.setHorizontalScrollBarPolicy(
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
areaScrollPane.setPreferredSize(new Dimension(780, 530));
areaScrollPane.setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder(""),
BorderFactory.createEmptyBorder(5,5,5,5)),
areaScrollPane.getBorder()));

// add scrollable text pane to the center panel
cen.add(areaScrollPane, BorderLayout.CENTER);

// define the open button to open logs
openButton = new JButton("Open Log");
openButton.setPreferredSize(new Dimension(125,25));
openButton.addActionListener(this);

// define the clear button to clear out stuff
clearButton = new JButton("Clear");
clearButton.setPreferredSize(new Dimension(125,25));
clearButton.addActionListener(this);

// define the verbose button
verboseButton = new JButton("Verbose [OFF]");
if (VERBOSE)
verboseButton.setText("Verbose [ON]");
verboseButton.setPreferredSize(new Dimension(125,25));
verboseButton.addActionListener(this);

// define the exit button
exitButton = new JButton("Quit");
exitButton.setPreferredSize(new Dimension(125,25));
exitButton.addActionListener(this);

// create the button panel
JPanel buttonPanel = new JPanel(
new FlowLayout(FlowLayout.CENTER, 30,5));
buttonPanel.add(openButton);
buttonPanel.add(clearButton);
buttonPanel.add(verboseButton);
buttonPanel.add(exitButton);

// add center panel and button panel to the container
c.add(cen, BorderLayout.CENTER);
c.add(buttonPanel, BorderLayout.SOUTH);

// add key listener to the textarea and all buttons (except quit)
textArea.addKeyListener(this);
openButton.addKeyListener(this);
clearButton.addKeyListener(this);
verboseButton.addKeyListener(this);
}


private void parseLog(String filename)
{
textArea.append(filename + "\n");
}


public void actionPerformed (ActionEvent e)
{
Object o = e.getSource();

if (o == openButton)
{
JFileChooser chooser = new JFileChooser();

if(chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
{
String fullFilePath = chooser.getSelectedFile().getAbsolutePath();
parseLog(fullFilePath);
}
}
else if (o == clearButton)
{
textArea.setText("");
}
else if (o == exitButton)
{
System.exit(0);
}
else if (o == verboseButton)
{
VERBOSE = !VERBOSE;

if (VERBOSE)
verboseButton.setText("Verbose [ON]");
else
verboseButton.setText("Verbose [OFF]");
}
}


public static void main(String[] args)
{
new ErrorChecker();
}


public void keyPressed(KeyEvent e)
{
// not used -- Auto-generated method stub
}


public void keyReleased(KeyEvent e)
{
// not used -- Auto-generated method stub
}


public void keyTyped(KeyEvent e)
{
System.out.println("Key typed " + num++);
// Only rely on the key char if the event is a key typed event.
if (e.getID() == KeyEvent.KEY_TYPED)
{
// get the key typed
int c = e.getKeyChar();

// check for modifiers
int modifiersEx = e.getModifiersEx();
String modCode = KeyEvent.getModifiersExText(modifiersEx);



/* Not sure why but Ctrl with some keys produces some weird codes.
* Here is a list of some codes we are looking for (some are expected
* such as 127 for 'Del' while Ctrl-O is funky):
*
* Ctrl-O = 15
* Delete = 127
*/
if (c == '-' && modCode.equals("Ctrl"))
decreaseFont();
else if (c == '=' && modCode.equals("Ctrl"))
increaseFont();
else if (c == 15 && modCode.equals("Ctrl"))
actionPerformed(new ActionEvent(openButton, 0, ""));
else if (c == 127 && modCode.equals("Shift"))
textArea.setText("");
}
}


private void increaseFont()
{
if (fontSize < MAX_FONT)
{
fontSize++;
textArea.setFont(new Font("monospaced", Font.PLAIN, fontSize));
}
}

private void decreaseFont()
{
if (fontSize > MIN_FONT)
{
fontSize--;
textArea.setFont(new Font("monospaced", Font.PLAIN, fontSize));
}
}
}

Martin O
April 7th, 2010, 04:30 PM
You can see some response from your program no matter what keys are pressed by making this change to your code:


public void keyPressed(KeyEvent e)
{
// not used -- Auto-generated method stub
System.out.println("key pressed " + num++);
}


For explanations about the difference between KeyListener.keyPressed and KeyListener.keyTyped, check the docs. Here's an excerpt from KeyEvent java docs:


No key typed events are generated for keys that don't generate Unicode characters (e.g., action keys, modifier keys, etc.).

Martin O
April 7th, 2010, 04:34 PM
BTW, that was hardly 'super stripped down'. Next time try to make your 'SSCCE' a lot more 'S'.

gamblor01
April 7th, 2010, 05:05 PM
Right...I stated that in one of my updates earlier that the keyPressed and keyReleased method both seem to respond.

In regard to the docs, I think what they mean is that keyTyped doesn't generate an event for modifier keys alone. So if you press Ctrl by itself then no event is generated. But if you press Ctrl in combination with another key, it should definitely generate a keyTyped event (and this works perfectly in a Linux environment).


I think ultimately I am just going to switch to the alt key since it seems to work perfectly. If anyone knows why this only seems to respond to certain ctrl sequences but not others...I would be very interested. I'm starting to wonder if there is a bug in the way the JVM is coded to handle these events.

gamblor01
April 7th, 2010, 05:11 PM
BTW, that was hardly 'super stripped down'. Next time try to make your 'SSCCE' a lot more 'S'.

Yeah I thought about putting less information in there but then I figured I would get comments like:

- "oh you're not showing us the whole code."
- "You probably did something wrong that we can't see."
- "You're probably just not adding the listener to the item that is in focus."

etc. etc.


And it *IS* stripped down. I removed a bunch of other methods that don't apply, remove javadoc comments, and this is only the class that contains my main method. There are many, many more classes that I'm not showing. ;)

goksanen
April 8th, 2010, 01:50 PM
Dear Sir: I think you have answered your own question. From the book Core Java 2 Vol 1, Horstmann, Cornell 2001, there is this note (p. 377). NOTE: Not all keystrokes result in a call to keyTyped. Only those keystrokes that generate a Unicode character can be captured in the keyTyped method. You need to use the keyPressed method to check for cursor keys and other command keys.

It appears that java was designed this way. Why it performed differently in Linux is odd!

It is suggested by the authors to trap raw keystrokes with keyPressed (ex. "P", F1 key) and keyReleased methods. The keyTyped method reports on the characters that were generated by user input, ex. "p" or "P".

Here is something that appears to work in your program. You can trap the Ctl Keys with the Virtual Key designations (ex. VK_MINUS) and the keyevent Control Key pressed down.
Interestingly enough the event.getKeyCode() works only under keyPressed. In the keyTyped method it always returns a 0.

Good luck. Your problem was interesting. Sincerely G.O.




public void keyPressed(KeyEvent e)
{
// not used -- Auto-generated method stub
int keyCode = e.getKeyCode();
System.out.println("Key pressed " + e.getKeyChar() + " Key Code " + e.getKeyCode() + " Modifier " + e.getModifiers());


if (keyCode == KeyEvent.VK_MINUS && e.isControlDown()) {
System.out.println("Ctl minus pressed");
decreaseFont();
}

}

gamblor01
April 8th, 2010, 09:30 PM
Interesting I'll try this out when I can. Right now keyTyped() works perfectly on Linux and os x. alt works fine in windows, even in keyTyped() so I really think ctrl is supposed to work that way too. ctrl alone never produces a keyTyped() event but ctrl with another key is supposed to (and does work with most keys!!!!).

I'll try your code soon...right now I test for the os type and then execute code as appropriate.

gamblor01
April 9th, 2010, 12:24 PM
You did it goksanen! Thank you so much for your help. It turns out that using the getKeyCode() instead of getKeyChar() method works properly inside of keyPressed(). I was able to reconfigure my code and it is MUCH cleaner now. Not only that, but the CTRL key works in Windows and Linux now, instead of using ALT for Windows and CTRL for Linux. Hooray -- behavior is consistent across both operating systems now!

I had to do some slightly different code for Mac OS X, but everything is very nice and easy now. FYI, here are the full implementations of keyPressed() and keyTyped():


public void keyPressed(KeyEvent e)
{
/*
* Determine OS type as we use this for Windows or Linux.
* For OS X we want to use the Command key instead of Ctrl
* so we rely on code in keyTyped().
*/
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("windows") || os.contains("linux"))
{
// determine the key that was pressed
int key = e.getKeyCode();

// figure out what to do
if (key == KeyEvent.VK_MINUS && e.isControlDown())
decreaseFont();
else if (key == KeyEvent.VK_EQUALS && e.isControlDown())
increaseFont();
else if (key == KeyEvent.VK_O && e.isControlDown())
actionPerformed(new ActionEvent(openButton, 0, ""));
else if (key == KeyEvent.VK_DELETE && e.isShiftDown())
textArea.setText("");
}
}


public void keyReleased(KeyEvent e)
{
// not used
}


public void keyTyped(KeyEvent e)
{
// determine OS type as we only use this method for OS X
String os = System.getProperty("os.name").toLowerCase();

if (os.contains("mac os") && e.getID() == KeyEvent.KEY_TYPED)
{
// get the key typed and any modifier
int key = e.getKeyChar();
int modVal = e.getModifiersEx();

// Define oKey since KeyEvent.KV_O is 79 but pressing 'o' is actually 111
int oKey = 111;

// make readable modifier code for Command key
String modCode = "";
if (modVal == 256)
modCode = "command";

// figure out what to do
if (key == KeyEvent.VK_MINUS && modCode.equals("command"))
decreaseFont();
else if (key == KeyEvent.VK_EQUALS && modCode.equals("command"))
increaseFont();
else if (key == oKey && modCode.equals("command"))
actionPerformed(new ActionEvent(openButton, 0, ""));
else if (key == KeyEvent.VK_DELETE && e.isShiftDown())
textArea.setText("");
}
}