Java Swing: consuming key events - java

I have a JFrame (containing various text fields and tables etc.) and want to install a hot key function that applies whenever the frame is open (a bit like a menu accelerator shortcut). The following mostly works, and my action is invoked regardless of which field or control has focus:
String MY_GLOBAL_ACTION_TRIGGER = "hotKey";
InputMap im = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
KeyStroke ks = KeyStroke.getKeyStroke('`');
im.put(ks, MY_GLOBAL_ACTION_TRIGGER);
ActionMap am = getRootPane().getActionMap();
am.put(MY_ACTION_TRIGGER, new AbstractAction() { public void actionPerformed() ... });
However, the key press isn't consumed and I still get a back quote inserted into the text field. How can I prevent the key press being propagated to text fields once my action has been invoked?

Use the KeyboardFocusManager and a KeyEventDispatcher
private void myListener implements KeyEventDispatcher {
public boolean dispatchKeyEvent (KeyEvent ke) {
if (ke.getKeyChar() == '`') {
MY_GLOBAL_ACTION.actionPerformed(null);
return true;
}
return false;
}
}

It is likely the text fields are getting precedence on the key event notification, meaning your key binding isn't getting notified until after the text field has been updated
Generally speaking, you really don't want to monitor key strokes/events on text components, as it does not take into consideration the use case where the user pastes text into field
If you want to filter content going into textField you should use a DocumentFilter
See Implementing a DocumentFilter

There are two issues here:
The KeyStroke constructed with char argument doesn't seem to actually catch the stroke. Try using KeyStroke(KeyEvent key, int modifiers).
The textfields should filter the selected stroke, or rather a listener should consume them.
Try something like:
public class KeyStrokeFrame extends JFrame {
public static void main(String[] args) {
new KeyStrokeFrame().setVisible(true);
}
public KeyStrokeFrame() {
setSize(200, 200);
JTextField jtf = new JTextField();
getContentPane().add(jtf);
String MY_GLOBAL_ACTION_TRIGGER = "hotKey";
InputMap im = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_1, 0);
((AbstractDocument)jtf.getDocument()).setDocumentFilter(new DocumentFilter() {
#Override
public void insertString(FilterBypass fb, int offset,
String string, AttributeSet attr)
throws BadLocationException {
if (string.equals("1")) return;
super.insertString(fb, offset, string, attr);
}
#Override
public void replace(FilterBypass fb, int offset, int length,
String text, AttributeSet attrs)
throws BadLocationException {
if (text.equals("1")) return;
super.replace(fb, offset, length, text, attrs);
}
});
im.put(ks, MY_GLOBAL_ACTION_TRIGGER);
ActionMap am = getRootPane().getActionMap();
am.put(MY_GLOBAL_ACTION_TRIGGER, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
System.out.println("pressed");}
});
}
}

Related

jtextarea is showing character for a moment when character is masked which shouldn't

I have a requirement where I am entering input in JTextarea at run time and input should be masked. I am able to achieve this through below code snippet
if(encryptKeystroke == true) {
jTextArea.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getExtendedKeyCode() == KeyEvent.VK_BACK_SPACE) {
if(text.length() > 0)
text = text.substring(0, text.length() - 1);
}
else {
text += String.valueOf(e.getKeyChar());
}
jTextArea.setText(text.replaceAll(".", "*"));
}
public void keyReleased(KeyEvent e) {
jTextArea.setText(text.replaceAll(".", "*"));
}
});
}
Issue is when I am running this, entered character is visible for a small moment and then getting masked(like it happened in Android).
I am using JTextarea because unable to achieve the scrollable & wrapstyle in Ttextfield.
Any suggestion how this can be achieved ?
Don't use a KeyListener for something like this. What if the user:
pastes text into the text area. The code won't handle multiple characters
moves the caret to the beginning of the text area. The code assumes text is always added at the end.
uses the Delete key
highlights a block of text and then enters a character
A KeyListener can't handle all these special situations. Swing has better and newer API's to use.
Instead you can use a DocumentFilter. The DocumentFilter allows you to filter the text BEFORE it is added to the Document of the JTextArea.
Basic example:
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
public class AsteriskFilter extends DocumentFilter
{
private StringBuilder realText = new StringBuilder();
#Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attributes)
throws BadLocationException
{
replace(fb, offset, 0, text, attributes);
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attributes)
throws BadLocationException
{
// Update the StringBuilder to contain the real text
Document doc = fb.getDocument();
realText.replace(offset, offset + length, text);
// Update the Document with asterisks
text = text.replaceAll(".", "*");
super.replace(fb, offset, length, text, attributes);
}
#Override
public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
throws BadLocationException
{
realText.delete(offset, offset + length);
super.remove(fb, offset, length);
}
public String getRealText()
{
return realText.toString();
}
private static void createAndShowGUI()
{
JTextArea textArea = new JTextArea(3, 20);
AbstractDocument doc = (AbstractDocument) textArea.getDocument();
AsteriskFilter filter = new AsteriskFilter();
doc.setDocumentFilter( filter );
JButton button = new JButton("Display Text");
button.addActionListener(e -> JOptionPane.showMessageDialog(textArea, filter.getRealText()));
JFrame frame = new JFrame("Asterisk Filter");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(textArea, BorderLayout.CENTER);
frame.add(button, BorderLayout.PAGE_END);
frame.setSize(220, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}

Prevent char entering on java swing JTextField

I'm using java swing and trying to enter Numbers only into a JTextField.
when typing a char I want to show an Invalid message, and prevent from typing the char into the JTextField.
idText.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
try
{
int i = Integer.parseInt(idText.getText()+e.getKeyChar());
validText.setText("");
}
catch(NumberFormatException e1) {
e.consume();
validText.setText("Numbers Only!");
}
}
});
for some reason, e.consume() doesnt work as I expected, and i can type chars.
Generally, adding a custom KeyListener to prevent characters in a JTextField is not recommended. It would be better for users (and easier for you as programmer) to use a component that it has been created for only-numbers input.
JSpinner is one of them.
JFormattedTextField is another one.
If you insist of doing it your self though, it would be better to achieve it with a DocumentFilter and not with a KeyListener. Here is another example as well.
In addition to these examples, here is my solution:
public class DocumentFilterExample extends JFrame {
public DocumentFilterExample() {
super("example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JTextField field = new JTextField(15);
AbstractDocument document = (AbstractDocument) field.getDocument();
document.setDocumentFilter(new OnlyDigitsDocumentFilter());
add(field);
setLocationByPlatform(true);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new DocumentFilterExample().setVisible(true));
}
private static class OnlyDigitsDocumentFilter extends DocumentFilter {
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
text = keepOnlyDigits(text);
super.replace(fb, offset, length, text, attrs);
}
#Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
string = keepOnlyDigits(string);
super.insertString(fb, offset, string, attr);
}
private String keepOnlyDigits(String text) {
StringBuilder sb = new StringBuilder();
text.chars().filter(Character::isDigit).forEach(sb::append);
return sb.toString();
}
}
}

Java JTextField Converting to upper/lowercase while writing

I've got question regarding to typing in JTextField. My program search thru few csv files and look for specified in JTextField string. I have add to readLine function ".toLowerCase" to read all strings as lowercase. Is it possible to set JTextField to automatically convert uppercase to lower case while writing to JTextField?
if (line.toLowerCase().contains(searchedString))...
Yes, you can use the KeyListener and when a key is pressed in the textfield, you will make the input string lowerCase while keeping the cursor position where it was. Like the code below:
jTextField1.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
int pos = jTextField1.getCaretPosition();
jTextField1.setText(jTextField1.getText().toLowerCase());
jTextField1.setCaretPosition(pos);
}
});
Source:
Value Change Listener to JTextField
Finding the cursor text position in JTextField
You can create a class that extends DocumentFilter class and override methods insertString and replace so that:
In insertString method it will call it's super, passing in one of the parameters string.toLowerCase()
In replace method it will call it's super, passing in one of the parameters text.toLowerCase()
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
class LowerCaseDocumentFilter extends DocumentFilter {
#Override
public void insertString(final FilterBypass fb, final int offset, final String string, final AttributeSet attr) throws BadLocationException {
super.insertString(fb, offset, string.toLowerCase(), attr);
}
#Override
public void replace(final FilterBypass fb, final int offset, final int length, final String text, final AttributeSet attrs) throws BadLocationException {
super.replace(fb, offset, length, text.toLowerCase(), attrs);
}
}
then adds an instance of this class so that the JTextField will automatically convert to lower case:
class Main {
public static void main(String[]args) {
JFrame jFrame = new JFrame("Example");
jFrame.setSize(500, 500);
jFrame.setVisible(true);
JPanel jPanel = new JPanel();
jFrame.add(jPanel);
JTextField jTextField = new JTextField("Example JTextField");
((AbstractDocument)jTextField.getDocument()).setDocumentFilter(new LowerCaseDocumentFilter());
jPanel.add(jTextField);
jFrame.pack();
}
}
Source:
https://stackoverflow.com/a/11573312
You can create your own class by extending the JTextfield and override constructor/setter method.

How to create a KeyEvent

When i create the KeyListener, it requires the following fields:
public void keyPressed(KeyEvent e)
{
}
public void keyReleased(KeyEvent e)
{
}
public void keyTyped(KeyEvent e)
{
}
When i put System.out.println(e) into the keyPressed method, though, it returns this when i press the enter key:
java.awt.event.KeyEvent[KEY_PRESSED,keyCode=10,keyText=?,keyChar=?,keyLocation=KEY_LOCATION_STANDARD,rawCode=0,primaryLevelUnicode=0,scancode=0] on javax.swing.JButton[,1,1,100x100,alignmentX=0.0,alignmentY=0.5,border=com.apple.laf.AquaButtonBorder$Dynamic#13b33a0e,flags=288,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=0,left=2,bottom=0,right=2],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=false,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=HI,defaultCapable=true]
This is obviously not a KeyEvent, so I cannot use it to call the keyPressed(KeyEvent e). What I want to be able to do is simulate the pressing of a key, specifically the enter key, in a way that would activate the keyListener and would output that text into a JTextArea.
Note: I looked at the accepted answer for How can I perfectly simulate KeyEvents?, and understood little of how it actually works, and i want code i understand. I also looked here How to simulate keyboard presses in java?, but not i could not get the robot to work; nothing happened when a key was supposed to be pressed.
e is the KeyEvent.
if you want to see the e value, then you can try this
System.out.println(e.getKeyChar());
Creating KeyEvent :
KeyEvent e = new KeyEvent(Component source, int id, long when, int modifiers, int keyCode, char keyChar, int keyLocation);
Example (dunno if this is the right way, but it produce the right output):
Button a = new Button("click");
KeyEvent e;
e = new KeyEvent(a, 1, 20, 1, 10, 'a');
System.out.println(""+e.getKeyChar());
System.out.println(""+e.getKeyCode());
Here is the all type of KeyEvent parameters
java.​awt.​event.​KeyEvent
#Deprecated public KeyEvent(Component source, int id, long when, int modifiers, int keyCode)
Deprecated. as of JDK1.1
===
java.​awt.​event.​KeyEvent
public KeyEvent(Component source, int id, long when, int modifiers, int keyCode, char keyChar)
Constructs a KeyEvent object.
Note that passing in an invalid id results in unspecified behavior. This method throws an IllegalArgumentException if source is null.
Parameters:
source - the Component that originated the event id - an integer identifying the type of event when - a long integer that specifies the time the event occurred modifiers - the modifier keys down during event (shift, ctrl, alt, meta) Either extended _DOWN_MASK or old _MASK modifiers should be used, but both models should not be mixed in one event. Use of the extended modifiers is preferred. keyCode - the integer code for an actual key, or VK_UNDEFINED (for a key-typed event) keyChar - the Unicode character generated by this event, or CHAR_UNDEFINED (for key-pressed and key-released events which do not map to a valid Unicode character)
Throws:
IllegalArgumentException - if id is KEY_TYPED and keyChar is CHAR_UNDEFINED; or if id is KEY_TYPED and keyCode is not VK_UNDEFINED IllegalArgumentException - if source is null
===
java.​awt.​event.​KeyEvent
public KeyEvent(Component source, int id, long when, int modifiers, int keyCode, char keyChar, int keyLocation)
When using robot, first obtain focus of component to which you add your KeyListener to. Then you can use robot to simulte key presses. As an alternative, you can just use dispatchEvent on component to which your listener is added.
KeyEvent key = new KeyEvent(inputField, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'Z');
inputField.dispatchEvent(key);
Providing you have:
JInputField InputField = new JInputField();
You can as well create KeyEvent as described above and pass it to keyTyped method of your listener.
As for keyPrssed, you can do the same.
You state:
I believe it would make part of my code more efficient. When certain conditions are met (I am doing hang man, and this is a "cheat" that is a joke with my teacher) the computer will press the correct keys to "guess" the answer. and then there is the simple, i wonder if i can? part of it. that got started when i saw JButton.doClick() and wondered if it had one for JTextFields
As I suspected, you are going about this all wrong. If you want your program to press keys for you, there's no need to create KeyEvents. If the "keys" are JButtons, then simply calling doClick() on the button will do. If you are desiring to fill text into a JTextField, then simply setting the text is all that is needed. i.e.,
For instance if you called the bit of text below in a Swing Timer (to slow it down so that you see the text being added:
String myText = myTextField.getText();
myText += nextBitOfText;
myTextField.setText(myText);
You would likely get the effect you desire.
For example:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.*;
#SuppressWarnings("serial")
public class AddTextToTextField extends JPanel {
public static final String[] POSSIBLE_TEXTS = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
public static final int TIMER_DELAY = 500;
private JTextField myTextField = new JTextField(20);
private JButton myButton = new JButton(new BtnAction("Press Me"));
private Random random = new Random();
public AddTextToTextField() {
add(myTextField);
add(myButton);
}
private class BtnAction extends AbstractAction {
public BtnAction(String text) {
super(text);
}
#Override
public void actionPerformed(ActionEvent arg0) {
setEnabled(false);
myTextField.setText("");
myTextField.setFocusable(false);
String randomText = POSSIBLE_TEXTS[random.nextInt(POSSIBLE_TEXTS.length)];
new Timer(TIMER_DELAY, new TimerAction(this, randomText)).start();
}
}
private class TimerAction implements ActionListener {
private AbstractAction btnAction;
private String text;
private int count = 0;
public TimerAction(AbstractAction btnAction, String text) {
this.btnAction = btnAction;
this.text = text;
}
#Override
public void actionPerformed(ActionEvent e) {
if (count <= text.length()) {
myTextField.setText(text.substring(0, count));
count++;
} else {
((Timer)e.getSource()).stop();
btnAction.setEnabled(true);
myTextField.setFocusable(true);
}
}
}
private static void createAndShowGui() {
AddTextToTextField mainPanel = new AddTextToTextField();
JFrame frame = new JFrame("AddTextToTextField");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
You can use this to dispatch a virtual event:
textArea.dispatchEvent(new KeyEvent(jFrame,
KeyEvent.KEY_TYPED, System.currentTimeMillis(),
0,
KeyEvent.VK_ENTER));
Try that out, it should work but that was from memory

java change the document in DocumentListener

I use a DocumentListener to handle any change in a JTextPane document. while the user types i want to delete the contents of JTextPane and insert a customized text instead. it is not possible to change the document in the DocumentListener,instead a solution is said here:
java.lang.IllegalStateException while using Document Listener in TextArea, Java
,but i don't understand that, at least i don't know what to do in my case?
DocumentListener is really only good for notification of changes and should never be used to modify a text field/document.
Instead, use a DocumentFilter
Check here for examples
FYI
The root course of your problem is that the DocumentListener is notified WHILE the document is been updated. Attempts to modify the document (apart from risking a infinite loop) put the document into a invalid state, hence the exception
Updated with an example
This is VERY basic example...It doesn't handle insert or remove, but my testing had remove working without doing anything anyway...
public class TestHighlight {
public static void main(String[] args) {
new TestHighlight();
}
public TestHighlight() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JTextPane textPane = new JTextPane(new DefaultStyledDocument());
((AbstractDocument) textPane.getDocument()).setDocumentFilter(new HighlightDocumentFilter(textPane));
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(textPane));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class HighlightDocumentFilter extends DocumentFilter {
private DefaultHighlightPainter highlightPainter = new DefaultHighlightPainter(Color.YELLOW);
private JTextPane textPane;
private SimpleAttributeSet background;
public HighlightDocumentFilter(JTextPane textPane) {
this.textPane = textPane;
background = new SimpleAttributeSet();
StyleConstants.setBackground(background, Color.RED);
}
#Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
System.out.println("insert");
super.insertString(fb, offset, text, attr);
}
#Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
System.out.println("remove");
super.remove(fb, offset, length);
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
String match = "test";
super.replace(fb, offset, length, text, attrs);
int startIndex = offset - match.length();
if (startIndex >= 0) {
String last = fb.getDocument().getText(startIndex, match.length()).trim();
System.out.println(last);
if (last.equalsIgnoreCase(match)) {
textPane.getHighlighter().addHighlight(startIndex, startIndex + match.length(), highlightPainter);
}
}
}
}
}
while the user types i want to delete the contents of JTextPane and
insert a customized text instead.
this isn't job for DocumentListener, basically this Listener is designed to firing events out from JTextComponents to the another JComponent, to Swing GUI, implemented methods in used Java
have look at DocumentFilter, this provide desired methods to change, modify or update own Document (model for JTextComponents) on runtime
Wrap the code you call in SwingUtilities.invokeLater()

Categories