I have created an external class that overrides JTextField. In the class I have added a DocumentListener which replaces prevents a certain character from being entered. Below is code (its not exact code, for purpose of simplicity of this question):
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
class test extends JTextField{
private String filteredText;
public void getDefaultText(){
super.getText();
}
public String getText(){
return filteredText;
}
public void remword(){
super.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void changedUpdate(DocumentEvent arg0) {
}
#Override
public void insertUpdate(DocumentEvent arg0) {
Runnable ru = new Runnable() {
#Override
public void run() {
String finalText = getDefaultText().replace("xyz", "asd");
setText(finalText);
filteredText = finalText;
}
}
};
SwingUtilities.invokeLater(ru);
}
#Override
public void removeUpdate(DocumentEvent arg0) {
}
});
}
}
Now this code works fine unless I use the getText() method which returns error Attempt to mutate in notificaton. I have tried messing around with synchronized and Runnable to no avail.
From DocumentListener:
The DocumentEvent notification is based upon the JavaBeans event model. There is no guarantee about the order of delivery to listeners, and all listeners must be notified prior to making further mutations to the Document. This means implementations of the DocumentListener may not mutate the source of the event (i.e. the associated Document).
You should use a DocumentFilter for control over document manipulation.
By the way, using getText() shouldn't throw this exception. You probably meant setText(String).
You don't need to subclass JTextField for this use case:
((AbstractDocument) textField.getDocument()).setDocumentFilter(new DocumentFilter() {
#Override
public void insertString(DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
super.insertString(fb, offset, sanitize(string), attr);
}
#Override
public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
super.replace(fb, offset, length, sanitize(text), attrs);
}
private String sanitize(String s) {
return s == null ? null : s.replace("xyz", "asd");
}
});
Beware that my solution would still allow "xyz" in the document if entered letter by letter or by removing e.g. "_" from "xy_z".
Related
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();
}
});
*/
}
}
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();
}
}
}
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.
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");}
});
}
}
I'm trying to set a documentFilter for my JTextArea. Having overriden the insert(...) method I admitted that it is never called. What's wrong? A piece of code:
package jaba;
import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
public class Main extends JFrame {
public Main() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(640, 480);
setLayout(new FlowLayout());
add(txt);
Document doc = txt.getDocument();
if (doc instanceof AbstractDocument) {
((AbstractDocument)doc).setDocumentFilter(new DocumentFilter() {
#Override
public void insertString(DocumentFilter.FilterBypass fb,
int offset, String string, AttributeSet att)
throws BadLocationException {
if (string.toLowerCase().contains("ass")) {
super.insertString(fb, offset, "###", att);
} else {
super.insertString(fb, offset, string, att);
}
}
});
} else {
txt.setText("error setting filter");
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Main().setVisible(true);
}
});
}
private JTextArea txt = new JTextArea(40, 40);
}
Having overriden the insert(...) method I admitted that it is never called.
Changes to the text in Swing components ultimately invoke the replace(...) method of the DocumentFilter.
The insertString(...) method is only invoked when you update the Document directly by using code like:
textField.getDocument().insertString(...);
So you need to make sure that you also override the replace() method in the DocumentFilter.