I was hoping to retrieve the newline character at the end of the text in a JTextField, however after setting the text field's text to my string, it has been modified to be a space character. The code below highlights the problem.
Is it possible to preserve the \n in the text field?
import javax.swing.JTextField;
public class StackOver {
public static void main(String[] args) {
String hasNewLineEscapeCharacter = makeStringWithNewLineCharacter();
JTextField backgroundText = new JTextField(90);
backgroundText.setText(hasNewLineEscapeCharacter);
char spaceChar = backgroundText.getText().charAt(backgroundText.getText().length()-1);
char newLineChar = hasNewLineEscapeCharacter.charAt(needsNewLineEscapeCharacter.length()-1);
}
public static String makeStringWithNewLineCharacter() {
String str = "hello,world!";
str += ('\n');
return str;
}
}
Is it possible to preserve the '\n' in my JTextField?
A JTextField uses a PlainDocument which contains a property that filters the "\n" and replaces it with a space character.
To preserve the newline character in the text field you can try:
textField.getDocument().putProperty("filterNewlines", Boolean.FALSE);
However, this will cause the text to display on two lines just like a text area.
So as suggested above you should just be using a JTextArea.
There is no way to display text that contains a newline character on a single line.
What you might want to do is store some other unprintable character in the text string and then use a String.replaceAll(...) if you ever need to access the text with the newline character added back in.
If you want your JTextField to not only display, but also accept newlines when the user presses the Enter key, you also need to add an ActionListener. If you subclass JTextField (you may use a factory method instead), it might look like this:
public class JMultilineTextField extends JTextField {
public JMultilineTextField() {
addActionListener(e -> {
try {
final int selectionStart = getSelectionStart();
final int selectionEnd = getSelectionEnd();
final Document document = getDocument();
if (selectionStart != selectionEnd) {
document.remove(selectionStart, selectionEnd - selectionStart);
}
document.insertString(selectionStart, "\n", null);
} catch (final BadLocationException ble) {
// Unlikely to happen, but add your error handling code here.
ble.printStackTrace();
}
});
}
}
But this brings another problem: your JTextField will grow or shrink vertically as newlines are added or removed, and you need the parent component to reflect the change in the text field size and layout its children accordingly (otherwise, you may well end up having overlapping Swing components). This problem can be solved by installing a DocumentListener into your text field's Document:
public class JMultilineTextField extends JTextField {
// ...
#Override
public void setDocument(final Document doc) {
setupDocument(doc);
super.setDocument(doc);
}
private void setupDocument(final Document doc) {
doc.putProperty("filterNewlines", false);
installDocumentListenerIfNecessary(doc);
}
private void installDocumentListenerIfNecessary(final Document doc) {
final DocumentListener documentListeners[] = ((AbstractDocument) doc).getListeners(DocumentListener.class);
if (Stream.of(documentListeners).noneMatch(documentListener -> documentListener instanceof RowCountChangeListener)) {
doc.addDocumentListener(new RowCountChangeListener());
}
}
private final class RowCountChangeListener implements DocumentListener {
#Override
public void insertUpdate(final DocumentEvent e) {
revalidateParentIfNecessary();
}
#Override
public void removeUpdate(final DocumentEvent e) {
revalidateParentIfNecessary();
}
#Override
public void changedUpdate(final DocumentEvent e) {
revalidateParentIfNecessary();
}
private void revalidateParentIfNecessary() {
final JMultilineTextField textField = JMultilineTextField.this;
final Dimension currentSize = textField.getSize();
final Dimension preferredSize = textField.getPreferredSize();
/*
* Grow/shrink vertically as necessary.
*/
if (currentSize.height != preferredSize.height) {
textField.getParent().revalidate();
}
}
}
}
I also suggest that you call setupDocument() from your text field's constructor:
public class JMultilineTextField extends JTextField {
public JMultilineTextField() {
setupDocument(getDocument());
// add ActionListener, etc.
}
// ...
}
Full code.
I have JFrame with 3 JPanel(basically three tabs). one of the panel has a textbox. there is value restriction on textbox. it means, user can enter only 1-1000 number in it. If he enters number >1000, it throws the warning message.
Now I am using focuslistener to save the entered number as soon as it looses the focus. But if the user enters 1200 and click on another tab(panel), it gives me expected warning message but also goes to the another tab. I need to remain in same panel if there is warning box. I don't want to loose the focus from the current panel.
mMaxLabelLength = new JTextField();
mMaxLabelLength.addActionListener(this);
public void focusGained(FocusEvent fe)
{
// do NOTHING
}
#Override
public void focusLost(FocusEvent fe)
{
saveActions();
}
public void actionPerformed(ActionEvent e)
{
//Do something
}
private void saveActions()
{
// error message
JOptionPane.showMessageDialog(this,
"Please enter an integer value between 1 and 1000.",
"Invalid Entry", JOptionPane.INFORMATION_MESSAGE);
SwiftApplication APP = SwiftApplication.getInstance();
int nMaxLabel = APP.getMaxPieLabel();
mMaxLabelLength.setText(new Integer(nMaxLabel).toString());
mMaxLabelLength.requestFocus();
}
The code block in the question does not offer too many details, but as far as I understand it, you need to use a VetoableChangeListener to prohibit focus change.
Here an example from Java2s:
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
public class Main {
public static void main(String[] argv) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addVetoableChangeListener(
new FocusVetoableChangeListener());
}
}
class FocusVetoableChangeListener implements VetoableChangeListener {
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
Component oldComp = (Component) evt.getOldValue();
Component newComp = (Component) evt.getNewValue();
if ("focusOwner".equals(evt.getPropertyName())) {
if (oldComp == null) {
System.out.println(newComp.getName());
} else {
System.out.println(oldComp.getName());
}
} else if ("focusedWindow".equals(evt.getPropertyName())) {
if (oldComp == null) {
System.out.println(newComp.getName());
} else {
System.out.println(oldComp.getName());
}
}
boolean vetoFocusChange = false;
if (vetoFocusChange) {
throw new PropertyVetoException("message", evt);
}
}
}
But, the more I think about it, maybe using InputVerifier and public boolean shouldYieldFocus(JComponent input) is more appropriate. See "Validating Input" in the "How to Use the Focus Subsystem" of the Java Tutorial.
Looks like you are looking for an InputVerifier.
Abstract class that allows input validation via the focus mechanism. When an attempt is made to shift the focus from a component containing an input verifier, the focus is not relinquished until the verifier is satisfied.
As the oracle page describes, it can be used to write own verifiers, which reject invalid inputs and keeps the focus meanwhile on the associated JComponent.
Therefore you just need to do two things:
Write your own InputVerifier, e.g. MyVerifier or take one of the already existing ones and create an instance of it. (See below for a small complete verifiable example)
Register your verifier instance on the target JComponent using calls to the Input Verification API.
This means, to register an...
InputVerifier call the setInputVerifier method of the JComponent class. For example, the InputVerificationDemo has the following code:
private MyVerifier verifier = new MyVerifier();
...
amountField.setInputVerifier(verifier);
Note For some reason I can't find a source for the java8 InputVerifier right now, it seems that the link is broken.
Small Verifiable Complete Example (from here)
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
// This program demonstrates the use of the Swing InputVerifier class.
// It creates two text fields; the first of the text fields expects the
// string "pass" as input, and will allow focus to advance out of it
// only after that string is typed in by the user.
public class VerifierTest extends JFrame {
public VerifierTest() {
JTextField tf1 = new JTextField ("Type \"pass\" here");
getContentPane().add (tf1, BorderLayout.NORTH);
tf1.setInputVerifier(new PassVerifier());
JTextField tf2 = new JTextField ("TextField2");
getContentPane().add (tf2, BorderLayout.SOUTH);
WindowListener l = new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
};
addWindowListener(l);
}
class PassVerifier extends InputVerifier {
public boolean verify(JComponent input) {
JTextField tf = (JTextField) input;
return "pass".equals(tf.getText());
}
}
public static void main(String[] args) {
Frame f = new VerifierTest();
f.pack();
f.setVisible(true);
}
}
I'm building a basic Point of Sale application and I've been looking for ways of having my main POS JFrame listen for bar code input. I found this code (slightly modified) posted by Cyrusmith, which looks like what I want but I don't know how to implement it in my JFrame. It looks like its intended to be a separate class, which is how I have it in my project currently. I asked my coworker and he doesn't know either.
Thanks for your help.
package barcode;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Listens for bar code input and puts it into a String Buffer.
*
*/
public class BarcodeReader {
private static final long THRESHOLD = 100;
private static final int MIN_BARCODE_LENGTH = 8;
public interface BarcodeListener {
void onBarcodeRead(String barcode);
}
private final StringBuffer barcode = new StringBuffer();
private final List<BarcodeListener> listeners = new CopyOnWriteArrayList<>();
private long lastEventTimeStamp = 0L;
public BarcodeReader() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
#Override
public boolean dispatchKeyEvent(KeyEvent e) {
try {
if (e.getID() != KeyEvent.KEY_RELEASED) {
return false;
}
if (e.getWhen() - lastEventTimeStamp > THRESHOLD) {
barcode.delete(0, barcode.length());
}
lastEventTimeStamp = e.getWhen();
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (barcode.length() >= MIN_BARCODE_LENGTH) {
fireBarcode(barcode.toString());
}
barcode.delete(0, barcode.length());
} else {
barcode.append(e.getKeyChar());
}
return false;
} catch (UnsupportedOperationException err) {
throw new UnsupportedOperationException(err); //To change body of generated methods, choose Tools | Templates.
}
}
});
}
protected void fireBarcode(String barcode) {
for (BarcodeListener listener : listeners) {
listener.onBarcodeRead(barcode);
}
}
public void addBarcodeListener(BarcodeListener listener) {
listeners.add(listener);
}
public void removeBarcodeListener(BarcodeListener listener) {
listeners.remove(listener);
}
}
Most bar code readers basically inject the codes directly into the keyboard buffer. So if you had a JTextField which had keyboard focus, the resulting text would be "entered" directly into it...no magic involved.
If you "want" to use this reader, then you will need to create an instance...
BarcodeReader reader = new BarcodeReader();
Register a BarcodeListener to it...
reader.addBarcodeListener(new BarcodeListener() {
public void onBarcodeRead(String barcode) {
// Respond to the event, like, I don't know,
// set the text of text field :P
}
});
But to me, this just seems like a lot of extra work - but that's just me...
So, yes, it's suppose to be a separate class. Depending on what you want to achieve, you could dump somewhere in your current code base, import the class into your source code and use it like any other. Equally, you could create a separate library for it, but this just means you need to include it within the classpath for compiling and runtime execution as well...
I want the message box to appear immediately after the user changes the value in the textfield. Currently, I need to hit the enter key to get the message box to pop out. Is there anything wrong with my code?
textField.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent e) {
if (Integer.parseInt(textField.getText())<=0){
JOptionPane.showMessageDialog(null,
"Error: Please enter number bigger than 0", "Error Message",
JOptionPane.ERROR_MESSAGE);
}
}
}
Any help would be appreciated!
Add a listener to the underlying Document, which is automatically created for you.
// Listen for changes in the text
textField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
warn();
}
public void removeUpdate(DocumentEvent e) {
warn();
}
public void insertUpdate(DocumentEvent e) {
warn();
}
public void warn() {
if (Integer.parseInt(textField.getText())<=0){
JOptionPane.showMessageDialog(null,
"Error: Please enter number bigger than 0", "Error Message",
JOptionPane.ERROR_MESSAGE);
}
}
});
The usual answer to this is "use a DocumentListener". However, I always find that interface cumbersome. Truthfully the interface is over-engineered. It has three methods, for insertion, removal, and replacement of text, when it only needs one method: replacement. (An insertion can be viewed as a replacement of no text with some text, and a removal can be viewed as a replacement of some text with no text.)
Usually all you want is to know is when the text in the box has changed, so a typical DocumentListener implementation has the three methods calling one method.
Therefore I made the following utility method, which lets you use a simpler ChangeListener rather than a DocumentListener. (It uses Java 8's lambda syntax, but you can adapt it for old Java if needed.)
/**
* Installs a listener to receive notification when the text of any
* {#code JTextComponent} is changed. Internally, it installs a
* {#link DocumentListener} on the text component's {#link Document},
* and a {#link PropertyChangeListener} on the text component to detect
* if the {#code Document} itself is replaced.
*
* #param text any text component, such as a {#link JTextField}
* or {#link JTextArea}
* #param changeListener a listener to receieve {#link ChangeEvent}s
* when the text is changed; the source object for the events
* will be the text component
* #throws NullPointerException if either parameter is null
*/
public static void addChangeListener(JTextComponent text, ChangeListener changeListener) {
Objects.requireNonNull(text);
Objects.requireNonNull(changeListener);
DocumentListener dl = new DocumentListener() {
private int lastChange = 0, lastNotifiedChange = 0;
#Override
public void insertUpdate(DocumentEvent e) {
changedUpdate(e);
}
#Override
public void removeUpdate(DocumentEvent e) {
changedUpdate(e);
}
#Override
public void changedUpdate(DocumentEvent e) {
lastChange++;
SwingUtilities.invokeLater(() -> {
if (lastNotifiedChange != lastChange) {
lastNotifiedChange = lastChange;
changeListener.stateChanged(new ChangeEvent(text));
}
});
}
};
text.addPropertyChangeListener("document", (PropertyChangeEvent e) -> {
Document d1 = (Document)e.getOldValue();
Document d2 = (Document)e.getNewValue();
if (d1 != null) d1.removeDocumentListener(dl);
if (d2 != null) d2.addDocumentListener(dl);
dl.changedUpdate(null);
});
Document d = text.getDocument();
if (d != null) d.addDocumentListener(dl);
}
Unlike with adding a listener directly to the document, this handles the (uncommon) case that you install a new document object on a text component. Additionally, it works around the problem mentioned in Jean-Marc Astesana's answer, where the document sometimes fires more events than it needs to.
Anyway, this method lets you replace annoying code which looks like this:
someTextBox.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
doSomething();
}
#Override
public void removeUpdate(DocumentEvent e) {
doSomething();
}
#Override
public void changedUpdate(DocumentEvent e) {
doSomething();
}
});
With:
addChangeListener(someTextBox, e -> doSomething());
Code released to public domain. Have fun!
Just create an interface that extends DocumentListener and implements all DocumentListener methods:
#FunctionalInterface
public interface SimpleDocumentListener extends DocumentListener {
void update(DocumentEvent e);
#Override
default void insertUpdate(DocumentEvent e) {
update(e);
}
#Override
default void removeUpdate(DocumentEvent e) {
update(e);
}
#Override
default void changedUpdate(DocumentEvent e) {
update(e);
}
}
and then:
jTextField.getDocument().addDocumentListener(new SimpleDocumentListener() {
#Override
public void update(DocumentEvent e) {
// Your code here
}
});
or you can even use lambda expression:
jTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> {
// Your code here
});
Be aware that when the user modify the field, the DocumentListener can, sometime, receive two events. For instance if the user selects the whole field content, then press a key, you'll receive a removeUpdate (all the content is remove) and an insertUpdate.
In your case, I don't think it is a problem but, generally speaking, it is.
Unfortunately, it seems there's no way to track the content of the textField without subclassing JTextField.
Here is the code of a class that provide a "text" property :
package net.yapbam.gui.widget;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
/** A JTextField with a property that maps its text.
* <br>I've found no way to track efficiently the modifications of the text of a JTextField ... so I developed this widget.
* <br>DocumentListeners are intended to do it, unfortunately, when a text is replace in a field, the listener receive two events:<ol>
* <li>One when the replaced text is removed.</li>
* <li>One when the replacing text is inserted</li>
* </ul>
* The first event is ... simply absolutely misleading, it corresponds to a value that the text never had.
* <br>Anoter problem with DocumentListener is that you can't modify the text into it (it throws IllegalStateException).
* <br><br>Another way was to use KeyListeners ... but some key events are throw a long time (probably the key auto-repeat interval)
* after the key was released. And others events (for example a click on an OK button) may occurs before the listener is informed of the change.
* <br><br>This widget guarantees that no "ghost" property change is thrown !
* #author Jean-Marc Astesana
* <BR>License : GPL v3
*/
public class CoolJTextField extends JTextField {
private static final long serialVersionUID = 1L;
public static final String TEXT_PROPERTY = "text";
public CoolJTextField() {
this(0);
}
public CoolJTextField(int nbColumns) {
super("", nbColumns);
this.setDocument(new MyDocument());
}
#SuppressWarnings("serial")
private class MyDocument extends PlainDocument {
private boolean ignoreEvents = false;
#Override
public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
String oldValue = CoolJTextField.this.getText();
this.ignoreEvents = true;
super.replace(offset, length, text, attrs);
this.ignoreEvents = false;
String newValue = CoolJTextField.this.getText();
if (!oldValue.equals(newValue)) CoolJTextField.this.firePropertyChange(TEXT_PROPERTY, oldValue, newValue);
}
#Override
public void remove(int offs, int len) throws BadLocationException {
String oldValue = CoolJTextField.this.getText();
super.remove(offs, len);
String newValue = CoolJTextField.this.getText();
if (!ignoreEvents && !oldValue.equals(newValue)) CoolJTextField.this.firePropertyChange(TEXT_PROPERTY, oldValue, newValue);
}
}
I know this relates to a really old problem, however, it caused me some problems too. As kleopatra responded in a comment above, I solved the problem with a JFormattedTextField. However, the solution requires a bit more work, but is neater.
The JFormattedTextField doesn't by default trigger a property change after every text changes in the field. The default constructor of JFormattedTextField does not create a formatter.
However, to do what the OP suggested, you need to use a formatter which will invoke the commitEdit() method after each valid edit of the field. The commitEdit() method is what triggers the property change from what I can see and without the formatter, this is triggered by default on a focus change or when the enter key is pressed.
See http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#value for more details.
Create a default formatter (DefaultFormatter) object to be passed to the JFormattedTextField either via its constructor or a setter method. One method of the default formatter is setCommitsOnValidEdit(boolean commit), which sets the formatter to trigger the commitEdit() method every time the text is changed. This can then be picked up using a PropertyChangeListener and the propertyChange() method.
An elegant way is to add the listener to the caret position, because it changes every time something is typed/deleted, then just compare old value with current one.
String oldVal = ""; // empty string or default value
JTextField tf = new JTextField(oldVal);
tf.addCaretListener(e -> {
String currentVal = tf.getText();
if(!currentVal.equals(oldVal)) {
oldVal = currentVal;
System.out.println("Change"); // do something
}
});
(This event is also being triggered every time a user just clicks into a TextField).
textBoxName.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
onChange();
}
#Override
public void removeUpdate(DocumentEvent e) {
onChange();
}
#Override
public void changedUpdate(DocumentEvent e) {
onChange();
}
});
But I would not just parse anything the user (maybe on accident) touches on his keyboard into an Integer. You should catch any Exceptions thrown and make sure the JTextField is not empty.
If we use runnable method SwingUtilities.invokeLater() while using Document listener application is getting stuck sometimes and taking time to update the result(As per my experiment). Instead of that we can also use KeyReleased event for text field change listener as mentioned here.
usernameTextField.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent e) {
JTextField textField = (JTextField) e.getSource();
String text = textField.getText();
textField.setText(text.toUpperCase());
}
});
it was the update version of Codemwnci. his code is quite fine and works great except the error message. To avoid error you must change the condition statement.
// Listen for changes in the text
textField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
warn();
}
public void removeUpdate(DocumentEvent e) {
warn();
}
public void insertUpdate(DocumentEvent e) {
warn();
}
public void warn() {
if (textField.getText().length()>0){
JOptionPane.showMessageDialog(null,
"Error: Please enter number bigger than 0", "Error Massage",
JOptionPane.ERROR_MESSAGE);
}
}
});
You can use even "MouseExited" to control.
example:
private void jtSoMauMouseExited(java.awt.event.MouseEvent evt) {
// TODO add your handling code here:
try {
if (Integer.parseInt(jtSoMau.getText()) > 1) {
//auto update field
SoMau = Integer.parseInt(jtSoMau.getText());
int result = SoMau / 5;
jtSoBlockQuan.setText(String.valueOf(result));
}
} catch (Exception e) {
}
}
Use a KeyListener (which triggers on any key) rather than the ActionListener (which triggers on enter)
DocumentFilter ? It gives you the ability to manipulate.
[ http://www.java2s.com/Tutorial/Java/0240__Swing/FormatJTextFieldstexttouppercase.htm ]
Sorry. J am using Jython (Python in Java) - but easy to understand
# python style
# upper chars [ text.upper() ]
class myComboBoxEditorDocumentFilter( DocumentFilter ):
def __init__(self,jtext):
self._jtext = jtext
def insertString(self,FilterBypass_fb, offset, text, AttributeSet_attrs):
txt = self._jtext.getText()
print('DocumentFilter-insertString:',offset,text,'old:',txt)
FilterBypass_fb.insertString(offset, text.upper(), AttributeSet_attrs)
def replace(self,FilterBypass_fb, offset, length, text, AttributeSet_attrs):
txt = self._jtext.getText()
print('DocumentFilter-replace:',offset, length, text,'old:',txt)
FilterBypass_fb.replace(offset, length, text.upper(), AttributeSet_attrs)
def remove(self,FilterBypass_fb, offset, length):
txt = self._jtext.getText()
print('DocumentFilter-remove:',offset, length, 'old:',txt)
FilterBypass_fb.remove(offset, length)
// (java style ~example for ComboBox-jTextField)
cb = new ComboBox();
cb.setEditable( true );
cbEditor = cb.getEditor();
cbEditorComp = cbEditor.getEditorComponent();
cbEditorComp.getDocument().setDocumentFilter(new myComboBoxEditorDocumentFilter(cbEditorComp));
I am brand new to WindowBuilder, and, in fact, just getting back into Java after a few years, but I implemented "something", then thought I'd look it up and came across this thread.
I'm in the middle of testing this, so, based on being new to all this, I'm sure I must be missing something.
Here's what I did, where "runTxt" is a textbox and "runName" is a data member of the class:
public void focusGained(FocusEvent e) {
if (e.getSource() == runTxt) {
System.out.println("runTxt got focus");
runTxt.selectAll();
}
}
public void focusLost(FocusEvent e) {
if (e.getSource() == runTxt) {
System.out.println("runTxt lost focus");
if(!runTxt.getText().equals(runName))runName= runTxt.getText();
System.out.println("runText.getText()= " + runTxt.getText() + "; runName= " + runName);
}
}
Seems a lot simpler than what's here so far, and seems to be working, but, since I'm in the middle of writing this, I'd appreciate hearing of any overlooked gotchas. Is it an issue that the user could enter & leave the textbox w/o making a change? I think all you've done is an unnecessary assignment.
Here is a Kotlin port of #Boann's answer, which is a great solution that has been working well for me.
import java.beans.*
import javax.swing.*
import javax.swing.event.*
import javax.swing.text.*
/**
* Installs a listener to receive notification when the text of this
* [JTextComponent] is changed. Internally, it installs a [DocumentListener] on the
* text component's [Document], and a [PropertyChangeListener] on the text component
* to detect if the `Document` itself is replaced.
*
* #param changeListener a listener to receive [ChangeEvent]s when the text is changed;
* the source object for the events will be the text component
*/
fun JTextComponent.addChangeListener(changeListener: ChangeListener) {
val dl: DocumentListener = object : DocumentListener {
private var lastChange = 0
private var lastNotifiedChange = 0
override fun insertUpdate(e: DocumentEvent) = changedUpdate(e)
override fun removeUpdate(e: DocumentEvent) = changedUpdate(e)
override fun changedUpdate(e: DocumentEvent) {
lastChange++
SwingUtilities.invokeLater {
if (lastNotifiedChange != lastChange) {
lastNotifiedChange = lastChange
changeListener.stateChanged(ChangeEvent(this))
}
}
}
}
addPropertyChangeListener("document") { e: PropertyChangeEvent ->
(e.oldValue as? Document)?.removeDocumentListener(dl)
(e.newValue as? Document)?.addDocumentListener(dl)
dl.changedUpdate(null)
}
document?.addDocumentListener(dl)
}
You can use it on any text component as follows:
myTextField.addChangeListener { event -> myEventHandler(event) }
Like his code, also public domain.