Key Binding not working, action not performed - java

I'm just trying to learn how the key binders work, and it seems I've misunderstood something from the Java tutorials. This is the code:
public class KeyBinder {
public static void main(String[] args) {
//making frame and label to update when "g" key is pressed.
JLabel keybinderTestLabel;
JFrame mainFrame = new JFrame();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setSize(300,75);
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
keybinderTestLabel = new JLabel("Press the 'g' key to test the key binder.");
mainFrame.add(keybinderTestLabel);
Action gPressed = new AbstractAction(){
#Override
public void actionPerformed(ActionEvent e) {
keybinderTestLabel.setText("Key Binding Successful.");
System.out.println("Key Binding Successful.");
//Testing to see if the key binding was successful.
}
};
keybinderTestLabel.getInputMap().put(KeyStroke.getKeyStroke("g"), "gPressed");
keybinderTestLabel.getActionMap().put("gPressed", gPressed);
/*
* from my understanding, these two lines map the KeyStroke event of the g key
* to the action name "gpressed", then map the action name "gpressed" to the action
* gpressed.
*
*/
}
}
From what I understand, I mapped the g keystroke to the action name "gPressed", then mapped that to the action gPressed. For some reason though, when I run the program, pressing the g key does not update the text label. What's the issue here? is the "g" keystroke not actually mapped to the g key on the keyboard?

So, from the JavaDocs
public final InputMap getInputMap()
Returns the InputMap that is used
when the component has focus. This is convenience method for
getInputMap(WHEN_FOCUSED).
Since JLabel is not focusable, this will never work, instead, you need to supply a different focus condition, for example...
keybinderTestLabel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW). //...
Also, and this is a personal preference... KeyStroke.getKeyStroke("g") using KeyStroke.getKeyStroke like this can be problematic as the String you supply needs be very precise in it's meaning, and I can never remember exactly how it should work (and it's not overly documented).
If the first suggestion fails to fix the issue, also try using KeyStroke.getKeyStroke(KeyEvent.VK_G, 0) instead

Related

Text editor: if certain key pressed want to cancel key event and give new functionality

I have a JEditorPane with text/html type. When an enter key is pressed with the editor focused, I want to do some checks about the state of the document and then override the enter key default functionality if conditions are met.
I believe this can be done with a KeyListener listening for a key, then consume the event if conditions are met to cancel the key making any change to the input. Testing this idea I'm just trying to consume the key event when any key is pressed. The key listener below is printing output when i press any key, but the characters are still getting inserted into the editor pane.
How can I stop the characters getting inserted altogether?
Thanks for your help.
String content = "";
String type = "text/html";
editor = new JEditorPane(type, content);
editor.setEditorKit(new HTMLEditorKit());
editor.setEditable(true);
editor.setPreferredSize(new Dimension(500, 500));
panel.add(editor);
editor.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e){
System.out.println("huh?");
e.consume();
}
});
EDIT----------
Removed key listener, and added instead
Action enter = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
System.out.println("enter!");
if ( condition == true ){
// default enter key behaviour
}
}
};
editor.getActionMap().put("insert-break", enter);
Ok I got rid of the KeyListener and added this, which prevents the default enter-key functionality which is great. But how would i insert a break (the default enter key behaviour) if my if clause is true?
I can't figure out how to trigger that on the editor programmatically.
You are over thinking it.
We save the Action because we want to invoke the actionPerformed(...) method of the Action.
Assuming the original Action is stored in the variable "original" the code would be:
if (condition == true)
original.actionPerformed( e );

How to define the keyStroke for the numpad - enter key

I'm about to use keybinding in a swing application for the num pad enter key, but the key is difficult to catch.
All examples I have seen rely on something like
key == KeyEvent.VK_KP_LEFT
where VK_KP_LEFT is some predefined value. Other options are to define a keystoke like this:
KeyStroke.getKeyStroke("control A");
KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_MASK);
but I have not found the "modifier" for numpad.
What is easily to obtain is the difference between the general and the numpad-enter:
All numpad-keys (indepently if switched in the numeric mode or not) get assigned the
getKeyLocation() == 4
(I spottetd this from key pressed / key released methods)
The question is:
How to properly prepare the keyStroke for numpad enter key to use it in the
inputMap.put(KeyStroke keyStroke, Object actionMapKey)
key binding method?
Thanks,
Tarik
If you're looking for binding Enter key you can use KeyEvent.VK_ENTER, ie:
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "someAction");
getActionMap().put("someAction", someAction);
Here is a short example:
import java.awt.event.*;
import javax.swing.*;
public class Test {
public static void main(String[] args) throws Exception {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.add(new JLabel("Hit Enter"));
Action someAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frame, "Got it");
}
};
panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "someAction");
panel.getActionMap().put("someAction", someAction);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
EDIT: VK_ENTER on numeric pad vs main keyboard
After some testing, it looks like it may not be possible to bind these keys separately.
The same KeyStroke is generated for both keys. The implementation of JComponent.processKeyBinding does not examine the KeyEvent, all it cares is KeyStroke in order to find the desired action.
SwingUtilities.notifyAction that is responsible for dispatching the actual action does not delegate all the details of the KeyEvent (only key, modifiers and when). So inside action there is no way to distinguish either as there is no details in ActionEvent.
If it worth the trouble, you can override processKeyBinding and add some logic if needed. You can also use KeyboardFocusManageraddKeyEventDispatcher() for blocking one of the keys.
How about this?
if(keyEvent.getKeyLocation() == KeyEvent.KEY_LOCATION_NUMPAD
&& keyEvent.getKeyCode() == KeyEvent.VK_ENTER)

About JTextFields and typing

I'm working on a JTexfield which sets its own text to the name of the key pressed when it has the focus on the window. I've managed to let it have only a word with the code:
#Override
public void keyReleased(KeyEvent ev) {
if (ev.getKeyCode() != 0) {
keyTrigger = ev.getKeyCode();
txtTrigger.setText(ev.getKeyText(keyTrigger));
}
}
#Override
public void keyTyped(KeyEvent ev) {
txtTrigger.setText("");
}
However it looks horrible when you press special keys like F1--12 or Ctrl because it keeps the last typed non-special key (for example, if you press 'T' and then 'Ctrl', the text in the field keeps being 't' until you release the 'Ctrl' key).
This is the code so far for the JTextField:
txtTrigger = new JTextField();
txtTrigger.setColumns(10);
txtTrigger.addKeyListener(this);
txtTrigger.setBounds(80, 5, 64, 20);
contentPane.add(txtTrigger);
What I want is the field to be empty until you release the key. How can I get the application working this way?
I don't think a editable text field is your best choice here. What I've done in the past is basically faked it.
I've generated a custom component that "looks" like a JTextField and, using my own KeyListener, I've added elements to the view (I did my own "key" renderer, but you could simply have a list of String elements that you can render).
Basically, when keyPressed is triggered, I would add the key code to a list (taking into consideration things like its modifier state). If another key event is triggered with the same key code, then you can ignore it.
When keyReleased is triggered, you can remove that keycode from the active list.
You can add the keylistener to the jframe or many components...
Just make sure that component has the focus.

FocusListener callback method doesn't work for separately instantiated objects from same variable?

I'm currently working on the GUI part of our group's first semester exam project, and I ran into a curious issue regarding callbacks and event handling.
A bit off topic: I have googled this SO hard, but have really failed to find any concrete examples of using callbacks in Java, so from what i've gathered... By definition... What i'm doing here is actually a callback (Would be awesome if you could explain why, or point me to a page that explains it thoroughly)
Here's the code:
private FocusListener callback = new FocusListener(){
#Override public void focusGained(FocusEvent e){
if(hasBeenSet){}else{tfield.setText("");hasBeenSet=true;}
} #Override public void focusLost(FocusEvent e){}};
...
tfield = new JTextField("Insert CPR number", 8);
constraint.gridx = 0;
constraint.gridy = 1;
constraint.gridwidth = 2;
panel.add(tfield, constraint);
tfield.addFocusListener(callback);
tfield = new JTextField("Type new password", 8);
constraint.gridx = 0;
constraint.gridy = 2;
panel.add(tfield, constraint);
tfield.addFocusListener(callback);
tfield = new JTextField("Retype new password", 8);
constraint.gridx = 0;
constraint.gridy = 3;
panel.add(tfield, constraint);
tfield.addFocusListener(callback);
When I start up the GUI, it has these three (3) textfields, and the idea is to remove the text when the user focuses on the textfield. This should work for all three textfields, but apparently, whatever textfield you click on, only the last textfield gets set to an empty string. I'm most curious about this since each object is instantiated individually.
The obvious workaround here, is to just rename tfield to like "tfield[1-3]", but would be great if you could humor me with this :)
Also: Please note that all the GUI code is actually inside the constructor. Yes, I know this is completely ridiculous, but our design suggests that all logic and data handling will happen after the constructor has completed... So there shouldn't be any threats here per say, but I don't know if this would somehow conflict the callback method for FocusListener.
Thank you. :)
P.S. For the "Re/type new password" fields, I do acknowledge the JComponent JPasswordField, and that will be changed after this problem is fixed. So no need for the security warning :)
The tfield variable holds the reference to the last instance of JTextField. The way to do what you want is this:
private FocusListener callback = new FocusListener() {
#Override public void focusGained(FocusEvent e){
JTextField jtf = (JTextField) e.getSource();
if(hasBeenSet){}else{jtf.setText("");hasBeenSet=true;}
}
...
Note: as your code reads at the moment, hasBeenSet will be shared across all 3 text fields.
Update:
Java has no support for closures, so when the focusGained runs, it sees the last value of tfield, not the value tfield had when the listerner was installed.
It looks like hasBeenSet is defined as a member of the outer class and as such focusGained is checking the same variable for all 3 textfields. Here is a way of handling what I think you're trying to do:
tfield = new JTextField("Insert CPR number", 8);
tfield.putClientProperty("originalText", tfield.getText());
Then in the focusGained:
#Override public void focusGained(FocusEvent e){
JTextField jtf = (JTextField) e.getSource();
if(jtf.getClientProperty("originalText").equals(jtf.getText())){
jtf.setText("");
}
}
The putClientProperty/getClientProperty methods are defined in JComponent class and so these are available in every Swing GUI component that inherits from JComponent. They store/retrieve an Object given a string. In this case the string "originalText" holds the value originally used to initialize the JTextField. Upon gaining focus, if the field still contains that value, it is set to blank. Likewise, you can perform a similar operation in focusLost where if the field is blank you set it to the value retrieved for "originalText".

Simulating enter key in Swing (without using Robot)

So I'm trying to write a JButton that will act like an enter key when pressed. It must be able to fool a JTextField that is in focus into calling its action listeners. It can not use the robot framework, because that will make every program think enter is pressed, which is a problem.
Here is the backstory:
I have a program (written in Swing) which allows someone to enter data in many textfields and other things by hitting enter after typing in the data. It works great.
However, most people that use it are using a second program at the same time which automatically listens for an enter key and shuts off a robot (for those of you who are familiar with FIRST robotics, I'm talking about the SmartDashboard and the Driver Station). There have been quite a few complaints about this. People want to enter data without disabling the robot. As it turns out, the SmartDashboard (the program people want to hit enter on) allows custom swing components to be run along with it.
not entirely sure if I understand your requirement correctly (will delete this if not) ...
You can manually dispatch an event to whatever component you want to address. In the case of wanting to dispatch to the focusOwner
find the focusOwner by querying the KeyboardFocusManager
create a keyEvent with the focusOwner as sender
dispatch that event to the focusOwner
Something like:
Action action = new AbstractAction("fake enter") {
#Override
public void actionPerformed(ActionEvent e) {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Component comp = manager.getFocusOwner();
KeyEvent event = new KeyEvent(comp,
KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0,
KeyEvent.VK_ENTER, KeyEvent.CHAR_UNDEFINED);
comp.dispatchKeyEvent(event);
}
};
JButton button = new JButton(action);
button.setFocusable(false);
Action textAction = new AbstractAction("text") {
#Override
public void actionPerformed(ActionEvent e) {
LOG.info("I'm the text action" + ((Component) e.getSource()).getName());
}
};
JComponent comp = Box.createVerticalBox();
for (int i = 0; i < 5; i++) {
JTextField field = new JTextField(20);
field.setName(": " + i);
field.setAction(textAction);
comp.add(field);
}
comp.add(button);
Edit
added some lines for actually playing with it (#Joe commented it's not working). Clicking the button triggers the action of the focused textField (here simply prints out the field's name) Local context is vista and jdk6u27.
You might try getRootPane().setDefaultButton() on the frame. There's an example here.
Grabbing the element with the focus and manually dispatching an enter event didn't quite work, but because I just wanted to effect various JTextField, I came up with a similar solution:
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner instanceof JTextField) {
((JTextField) focusOwner).postActionEvent();
}
}
});
Thanks for pointing me in the right direction.

Categories

Resources