java change the document in DocumentListener
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);
}
}
}
}
}
Getting Changed Text from DocumentListener
As TimeLineTextClass
extends JFormattedField
, you'll find there actually is a getText() method.
@Override
public void insertUpdate(DocumentEvent e) {
fieldList.put(field,getText());
}
@Override
public void removeUpdate(DocumentEvent e) {
fieldList.put(field,getText());
}
The above code should work fine.
Java Swing - Detecting a change in the document
How does your this even compile
notepad = new JTextArea();
notepad.addTextListener(new TextListener() {
// ....
}
since TextListeners are not defined to work with JTextAreas but rather with TextAreas, a completely different beast.
You should add a DocumentListener to your JTextArea's Document.
notepad.getDocument().addDocumentListener(new DocumentListener() {
void insertUpdate(DocumentEvent e) {
documentChanged = true;
}
void removeUpdate(DocumentEvent e) {
documentChanged = true;
}
void changedUpdate(DocumentEvent e) {
documentChanged = true;
}
});
Regarding
My question is, is there any way to use both TextListeners and ActionListeners (or any other listener) simultaneously in the same class?
Use of a DocumentListener has nothing to do with ActionListeners used elsewhere in your program since their domains are orthogonal to each other, i.e., the one has absolutely nothing to do with the other.
Swing JTextField text changed listener DocumentListener infinity loop
There are two approaches to properly solve this:
Fire a property change event only if actually something changed
There's no point in notifying if the property value is the same as before (not changed at all). Avoiding the unnecessary event will effectively break your loop:
public void setHost(String host) {
// check if property actually changed
if (Objects.equals(this.host, host) return;
String oldHost = this.host;
this.host = host;
this.firePropertyChange("Host", oldHost, this.host);
}
or (fancy compact form):
public void setHost(String host) {
if (!Objects.equals(this.host, host)) {
firePropertyChange("Host", this.host, this.host=host);
}
}
Do one-way synchronization to avoid cascades
Changing a propery in the model can change a property in the view can change a property in the model can change... - this can quickly run in circles.
To break these cascades, do a one-way synchronization:
while the model notifies the view about changes, ignore any cascading updates.
To do so, you need a flag on the controller (in your case, the view holding the microcontrollers a.k.a. Swing listeners):
MyView.java:
boolean updating;
@Override public void modelPropertyChange(final PropertyChangeEvent event)
{
if (updating) {
// cascading update, ignore
return;
}
updating=true;
try {
if(event.getPropertyName().equals("Username")) {
{
String username = (String) event.getNewValue();
this.nameField.setText(username);
}
...
}
finally {
updating=false;
}
}
The first approach is pretty straightforward (but can get complicated when dealing with complex objects and collections).
The second approach is easy and more forgiving by design - view always represents model (no changes missed), and cascading updates are blocked.
How to remove content of JTextField while DocumentListener is running?
In general, you don't -- you don't change the state of the Document while listening to it when using a DocumentListener. The two possible solutions that I know of:
- From within your Listener, put the code that makes the changes that you wish to make within a Runnable and queue the Runnable onto the Swing event thread by calling
SwingUtilities.invokeLater(yourRunnable)
. This is a shameless kludge - Much better: Don't use a DocumentListener but rather a DocumentFilter since this type of listener was geared towards making changes to the Document before the text is visualized within the component.
Unrelated side issue: your code shows a worrisome degree of coupling in that you try to change the text in a specific text component from within your listener. DocumentListeners should be fully agnostic of the text component whose document that they listen to, and in fact may be added to more than one Document.
A DocumentFilter has 3 methods that need to be overridden and do what you expect them to do: what you would expect them to do:
insertString
: insert a String into the documentremove
: removes text from the documentreplace
: replaces text in the document
What is more, these methods do their actions before the text component renders the changes to the document.
So within my method overrides, I extract the current document's text, and use the parameters to create what the new text will look like, for example for the replace method I did:
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
String currentText = fb.getDocument().getText(0, fb.getDocument().getLength());
StringBuilder sb = new StringBuilder(currentText);
String newText = sb.replace(offset, offset + length, text).toString();
I then do a boolean test on the newText to see if it is "good", and if so, call the super's method, here replace(...)
, passing in all the parameters. If not, if the newText fails the test, then I remove all the text from the document and show a JOptionPane.
So in this example, I use this as my test method:
private boolean isTextOk(String text) {
return !BAD_TEXTS.contains(text);
}
which tests to see if the text is any of the disallowed Strings, here "...", " ", "oops", "OOPS"
, but it could be any Strings that you desire. Again, if the text passes the text, call the super's method, else remove the text:
if (isTextOk(newText)) {
super.replace(fb, offset, length, text, attrs);
} else {
badText(fb);
}
Where badText(fb)
does:
private void badText(FilterBypass fb) throws BadLocationException {
remove(fb, 0, fb.getDocument().getLength());
JOptionPane.showMessageDialog(null, "Don't do this!", "Bad Text Entered",
JOptionPane.WARNING_MESSAGE);
}
The whole example is:
import java.util.Arrays;
import java.util.List;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.PlainDocument;
@SuppressWarnings("serial")
public class ClearThreeDots extends JPanel {
private JTextField textField = new JTextField(40);
public ClearThreeDots() {
((PlainDocument) textField.getDocument()).setDocumentFilter(new MyDocFilter());
add(textField);
}
private static void createAndShowGui() {
ClearThreeDots mainPanel = new ClearThreeDots();
JFrame frame = new JFrame("Clear Three Dots");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MyDocFilter extends DocumentFilter {
private static final List<String> BAD_TEXTS = Arrays.asList("...", " ", "oops", "OOPS");
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
String currentText = fb.getDocument().getText(0, fb.getDocument().getLength());
StringBuilder sb = new StringBuilder(currentText);
String newText = sb.insert(offset, string).toString();
if (isTextOk(newText)) {
super.insertString(fb, offset, string, attr);
} else {
badText(fb);
}
}
@Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
String currentText = fb.getDocument().getText(0, fb.getDocument().getLength());
StringBuilder sb = new StringBuilder(currentText);
String newText = sb.replace(offset, offset + length, "").toString();
if (isTextOk(newText)) {
super.remove(fb, offset, length);
} else {
badText(fb);
}
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
String currentText = fb.getDocument().getText(0, fb.getDocument().getLength());
StringBuilder sb = new StringBuilder(currentText);
String newText = sb.replace(offset, offset + length, text).toString();
if (isTextOk(newText)) {
super.replace(fb, offset, length, text, attrs);
} else {
badText(fb);
}
}
private boolean isTextOk(String text) {
return !BAD_TEXTS.contains(text);
}
private void badText(FilterBypass fb) throws BadLocationException {
remove(fb, 0, fb.getDocument().getLength());
JOptionPane.showMessageDialog(null, "Don't do this!", "Bad Text Entered",
JOptionPane.WARNING_MESSAGE);
}
}
Get changed content in DocumentEvent
DocumentEvent has getOffset() and getLength() methods. Normally images are leaves so you can use htmlDocument.getCharacterElement() from the range of change and check whether they are images.
UPDATE: as result of discusing in commetns
You can add a DocumentFilter overriding remove() and use it as listener to get deleted fragment (in fact fragment to-be-deleted).
Document listener for multiple text fields
Well, you could assign the DocumentListener
to a variable
DocumentListener docListener = new DocumentListener() {
...
};
And the use it for all your textfields
filterR.getDocument().addDocumentListener(docListener);
filterR1.getDocument().addDocumentListener(docListener);
filterR2.getDocument().addDocumentListener(docListener);
...
Related Topics
Implementation Difference Between Aggregation and Composition in Java
Java - How to Receive Point Coordinates After Mouse Button Release (Jfreechart)
Spring Data: Override Save Method
Injecting Beans into a Class Outside the Spring Managed Context
Comparing Time Is Incorrect When Picking 12:00
How to Copy File Inside Jar to Outside the Jar
Mocking Files in Java - Mock Contents - Mockito
Java Local VS Instance Variable Access Speed
"Cannot Create Generic Array of .." - How to Create an Array of Map<String, Object>
How to Sort Two Arrays in Relation to Each Other
What Does This Thread Join Code Mean