How to capture key events - java

All of the examples for key listeners I have been able to find deal with components other than the main panel such as a text box or Menu.
I know how to use setMnemonic to program Menu hotkeys but this method does not seem to be available and the link to the oracle keylistener tutorial is broken.
When I do a Right Click > Events > Key > KeyPressed on the main form I get the following but none of keys cause mainPanelKeyPressed.
What is the correct way to use the key events to trigger an action independent of the focus?
mainPanel.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(java.awt.event.KeyEvent evt) {
mainPanelKeyPressed(evt);
}
});
private void mainPanelKeyPressed(java.awt.event.KeyEvent evt) {
// Added to help find the ID of each 'arrow' key
JOptionPane.showMessageDialog(null, "mainPanelKeyPressed");
}

What is the correct way to use the key events to trigger an action independent of the focus?
See: How to Use Key Bindings
Or use a JMenuBar with menus and menu items.

the focus is important. you may need to click around and experiment, and use component.requestFocusInWindow() to help.

Related

Non-editable JEditorPane consumes Enter key

I have an JDialog containing a JEditorPane for showing non-user-editable HTML content, such as Help and Release Notes.
The JDialog has a "Close" button that is installed as the default button.
If the JEditorPane is left "focusable", then the Page Up/Down keys will scroll through the document, but pressing "Enter" does not fire the default button.
On the other hand, if the JEditorPane is set non-focusable, then Page Up/Down keys do not work, but pressing the "Enter" key does fire the default button, closing the dialog.
#SuppressWarnings("serial")
public class NoteViewer extends JFrame {
public static void main(String[] args) throws IOException {
final NoteViewer viewer = new NoteViewer(new URL("http://example.com/"));
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
viewer.setVisible(true);
}
});
}
public NoteViewer(URL url) throws IOException {
super("Note Viewer");
setSize(900, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JEditorPane notes = new JEditorPane(url);
notes.setEditable(false);
// notes.setFocusable(false);
getContentPane().add(new JScrollPane(notes), BorderLayout.CENTER);
JButton close = new JButton("Close");
close.addActionListener(EventHandler.create(ActionListener.class, this, "dispose"));
Box box = Box.createHorizontalBox();
box.add(Box.createHorizontalGlue());
box.add(close);
getContentPane().add(box, BorderLayout.PAGE_END);
getRootPane().setDefaultButton(close);
}
}
Uncomment the notes.setFocusable(false) line to see the different behavior.
I would like the JEditorPane to process the navigation keystrokes (such as Page Up/Down), but ignore (not consume) the "editing" keystrokes, such as Enter, so that the Root Pane will invoke the default button.
After much hacking and single-stepping, I've got the behaviour I'm looking for with this code:
notes.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
"pressed");
but I am concerned that it is rather fragile. Do all platforms use VK_ENTER to invoke the default button? Do all platforms use "pressed" as the command-string to invoke the default button?
Finally, it is going about it the wrong way: instead of the JEditorPane ignoring the Enter key and letting the processing happen in an ancestor, this is the JEditorPane explicitly handling the enter key, which strikes me as wrong.
A non-editable JEditorPane should not capture all the editing keystrokes (A-Z, 0-9, Enter, Delete, etc.) and transform them into a warning beep, but rather leave them unhandled so that parent components get a chance. How can this be achieved in a general, non keystroke-by-keystroke input map fashion?
Do all platforms use "pressed" as the command-string to invoke the default button?
That is not what is happening.
You are mapping the Enter key to the "pressed" Action of the JEditorPane. However, there is no "pressed" Action so the Binding is ignored and the event is passed up to the root pane where the Enter key binding for the default button is used.
Normally "none" is used to indicate you want to ignore/remove the binding. Check out the section from the Swing tutorial on How to Remove Bindings for more information.
So I would say you solution is correct and should work on all platforms.
You may want to check out Key Bindings for a programs that displays all the key bindings for each component. You will see that there is no "pressed" action. JEditorPane actually uses "insert-break" to map to the Action.
A non-editable JEditorPane should not capture all the editing keystrokes (A-Z, 0-9, Enter, Delete, etc.) and transform them into a warning beep,
I don't have a "beeping" problem with a-z, 0-9. I do have a problem with the delete and backspace keys.
I'm using JDK8_45 on Windows 7.
Maybe you can prevent the dispatching of keys by using a KeyEventDispatcher. Maybe you check the source of the KeyEvent and if the source is the editor pane you only let the Enter key through? Might also need to allow the PageUp/PageDown events so scrolling will work.
Check out Global Event Dispatching for more information.
I think I just found a better way: set the JEditorPane as not editable and not focusable, and the JScrollPane as focusable.
JEditorPane notes = new JEditorPane(url);
notes.setEditable(false);
notes.setFocusable(false);
JScrollPane scroller = new JScrollPane(notes);
scroller.setFocusable(true);
getContentPane().add(scroller, BorderLayout.CENTER);
The Enter key is forwarded to the default button. Backspace, delete and friends don't generate any beeps.
Edit:
Doesn't allow selecting and copying of any text, so perhaps not the best solution.

KeyEvent getActionCommand for f9 key?

My code for handling some keypresses was working fine when the keyboard keys were all normal (a-z), but now I want to make the default screenshot key be F9.
if (e.getActionCommand().toUpperCase().equals(configFile.getProperty("TOGGLE_ATTACK_KEY"))){
inAttackMode = !inAttackMode;
} else if (e.getActionCommand().toUpperCase().equals(configFile.getProperty("SCREENSHOT_KEY"))){
e.getActionCommand() is returning null when I press the F9 key. The code to register this key is here:
theDesktop.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released " + configFile.getProperty("SCREENSHOT_KEY")), "f9ButtonRelease");
theDesktop.getActionMap().put("f9ButtonRelease", ClassKeyReleaseHandler);
Thanks for any help on this... I tried to search Google and SO but did not see anything specific. Also tried using VK_F9 to register, but it only fires with F9(either way it returns null when I press F9). Thanks for any help.
One of the reasons for using Key Bindings is to avoid the use of nested if/else statements. Instead you create a unique Action for the key binding, then the action command is irrelevant.
//theDesktop.getActionMap().put("f9ButtonRelease", ClassKeyReleaseHandler);
theDesktop.getActionMap().put("f9ButtonRelease", ScreenShotReleaseHandler);
This is the way all the default Actions are created in Swing.
UPDATE: not relevant for OP's question.
If you want to use the constant KeyEvent.VK_F9. You should not be using e.getActionCommand, but e.getKeyCode.
for example:
public class TestListener implements KeyListener{
public void keyPressed(KeyEvent e){
if(e.getKeyCode() == KeyEvent.VK_F9)
System.out.println("F9 is pressed");
}

How to limit ModifyListener for user interaction only

I have a textbox with attached ModifyListener.
In implemented modifyText(ModifyEvent e) I execute desired functionality.
The problem with that, that this event is triggered on every text change.
I don't want it to trigger if text was altered programmaticly (by setting text via code).
I want it to trigger only when user changes the code (I can't use keylistener because it will be triggered also when user click on arrow buttons and etc, it also won't detect if user copy&paste text)
You could unregister your ModifyListener before calling setText(..) and reregister it afterwards.
How about textBox.addKeyListener(...) and textBox.addMouseListener(...) instead of ModifyListener?
You can try using Focusout listener.... then you will get the value which user has entered only once.
Text text;
text.addListener(SWT.FocusOut, new Listener() {
#Override
public void handleEvent(Event arg0) {
//Your code here.....
}
});

Is it possible to use Enter as Tab without inheriting JTextField or mass-adding key listeners?

I've found several pages and SO answers about the enter-as-tab problem in Java, but all propose either overriding methods of JTextField or adding a key listener to every component.
But isn't there any other way? Can't I override something of the LookAndFeel or install some global policy?
After some documentation crawling I found a solution: It is possible to set the focus traversal keys on KeyboardFocusManager instead of a JComponent instance.
// 1. Get default keys
Set<AWTKeyStroke> ftk = new HashSet<AWTKeyStroke>(
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.getDefaultFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
// 2. Add our key
ftk.add(KeyStroke.getKeyStroke("ENTER"));
// 3. Set new keys
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.setDefaultFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, ftk);
This adds the enter key to the list of keys which are used for forward traversal. (Backward traversal similar)
you can probably use http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html
to change the keyBinding for the enter key
or you can add focustravesal keys
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, your keys here);
The hammer - of setting the enter as focus traversal key for all component except those which register their own - is just fine if it's really required. The obvious drawback is that default bindings to the enter stop working, in particular
action/Listeners on textFields
default buttons
any other component type with a custom binding to enter
If those side-effects are problematic, there's the less intrusive alternative of tweaking the binding in the shared ancestor actionMap of the textFields.
// "early" in the app instantiate a textField
JTextField text = new JTextField();
ActionMap map = text.getActionMap();
// get a reference to the default binding
final Action notify = map.get(JTextField.notifyAction);
while (map.getParent() != null) {
// walk up the parent chain to reach the top-most shared ancestor
map = map.getParent();
}
// custom notify action
TextAction tab = new TextAction(JTextField.notifyAction) {
#Override
public void actionPerformed(ActionEvent e) {
// delegate to default if enabled
if (notify.isEnabled()) {
notify.actionPerformed(e);
}
// trigger a focus transfer
getTextComponent(e).transferFocus();
}
};
// replace default with augmented custom action
map.put(JTextField.notifyAction, tab);
After replacing the default, all textFields will use the custom action. The one beware is that the replacement has to be repeated whenever the LAF is changed.

SWT, Maintain default tab ordering when adding Key Listner

I've been creating a custom TabFolder extension that adds a key listener to allow quick tab switching using an ALT + # hotkey.
By adding the KeyAdapter to my TabFolder, the event handler works properly only when you have a tab header selected (in which case the ALT + ARROW_LEFT/ARROW_RIGHT also work.). I need this hot key to be active when any Widget with-in the TabFolder is active; however, it shouldn't be active if the selection is in a different tab folder or widget outside of a tab folder.
In an attempt to solve this, I wrote a simple recursive function to apply the key listener to all of the children of the tab folder:
public void applyQuickSwitchKeyBindings() {
removeKeyListener(ka);
addKeyListener(ka);
for(Control c: getChildren())
applyQuickSwitchKeyBindingsToChildren(c);
}
private void applyQuickSwitchKeyBindingsToChildren(Control c) {
if(c==null) return;
if(c instanceof Composite) {
Control[] controls = ((Composite)c).getChildren();
for(Control c2: controls)
applyQuickSwitchKeyBindingsToChildren(c2);
if(controls.length < 1) {
c.removeKeyListener(ka);
c.addKeyListener(ka);
}
}
}
Then i call the applyQuickSwitchKeyBindings() after I add the controls to each TabItem in the tab group.
The good news was that the quick switch hot key (ALT + #) worked great!
The bad news was that the original TAB ordering based on z-index is now gone. When you hit the SWT.TAB key you lose focus on your current text box and don't gain focus on anything else...
Questions:
1.) Can each control only have one KeyListener?
2.) Why is the original TAB traversal not working anymore?
Thanks in advance!
to 1) I'm pretty sure that more than one KeyListener is allowed.
to 2) I'm not sure, that depends on what you're doing in your KeyAdapter. Maybe you can post that too?
I just the tab order is broken somehow, you can reset ( or change ) it with a call to setTabList( Control[] ).
setTablList( new Control[] {
control1,
control2,
control3,
....
} );
So after more time learning and developing with SWT i've discovered my problem. When you add a listener it is applied to the widget/control you call the addXXXListener function on. So if that control is not active the listeners will not be fired.
The solution seems to be SWT's global Filter mechanism which allows you to add global application(Display) scope listeners.
Display.getCurrent().addFilter(SWT.keyPress, new KeyPressListener());
Pardon the incorrectness of this line, but if you google it you'll see what i mean.
I have also read to use this sparingly.

Categories