Document Model in Java GUI
I have two JTextAreas in my GUI, and I have a DocumentListener on each JTextArea, what I'm trying to do is for example when I type abc in text area number 1 it will take that document text modify it in some way and output it in the Document for JTextArea 2.
with my Listener I can get the source document I can get the text I can modify the text but when I try to put it back into the document I get an error
Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification
Please help.
Thanks
Here's some code:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* @author Maxi
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.*;
import javax.swing.text.*;
public class Test {
static JFrame frame = new JFrame("CaesarEncipherGUI");
static JPanel panel = new JPanel();
static JTextArea area = new JTextArea(5,20);
static JTextArea area1 = new JTextArea(5,20);
static class MyDocumentListener2 implements DocumentListener {开发者_运维百科
public void insertUpdate(DocumentEvent e) {
updateLog(e,"");
}
public void removeUpdate(DocumentEvent e) {
updateLog(e,"");
}
public void changedUpdate(DocumentEvent e) {
}
public void updateLog(DocumentEvent e, String action){
Document doc = (Document)e.getDocument();
try{
System.out.println("Action detected "+doc.getProperty("type"));
String text = doc.getText(0, doc.getLength());
doc.insertString(0, "hey", null); //heres the line that throws the error.
//mutation of text here
}catch (BadLocationException catchme2){}
}
}
public static void main(String[] args){
area.getDocument().addDocumentListener(new MyDocumentListener2());
//initialize
frame.setResizable(false);
frame.setBounds(300, 300, 235, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.add(area);
panel.add(area1);
frame.add(panel);
frame.setSize(235,400);
frame.setVisible(true);
}
}
Likely you are trying to have the DocumentListener change the text on the same Document that it is listening to. This is not allowed as per the DocumentListener API which states:
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).
One way around this is to place your method to change the Document's text in a Runnable and to queue it on the EDT with SwingUtilities.invokeLater(...)
.
Another solution, perhaps better, is to use a DocumentFilter
.
example with DocumentListener:
static class MyDocumentListener2 implements DocumentListener {
private boolean updating = false;
public void insertUpdate(DocumentEvent e) {
updateLog(e, "");
}
public void removeUpdate(DocumentEvent e) {
updateLog(e, "");
}
public void changedUpdate(DocumentEvent e) {
}
public void updateLog(DocumentEvent e, String action) {
if (updating) {
return;
}
updating = true;
final Document doc = (Document) e.getDocument();
try {
System.out.println("Action detected " + doc.getProperty("type"));
final String text = doc.getText(0, doc.getLength());
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
doc.insertString(0, "hey", null);
updating = false;
} catch (BadLocationException e) {
e.printStackTrace();
}
}
});
} catch (BadLocationException catchme2) {
catchme2.printStackTrace();
}
}
}
And a DocumentListener and DocumentFilter example that turns all text to upper case:
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class Foo003 {
private static final String ENTER = "enter";
public static void main(String[] args) {
final JTextArea myArea = new JTextArea(10, 20);
final PlainDocument myDocument = (PlainDocument) myArea.getDocument();
DocumentListener myDocumentListener = new DocumentListener() {
private boolean changing = false;
public void removeUpdate(DocumentEvent e) {}
public void changedUpdate(DocumentEvent e) {
toUpperCase(myArea, myDocument);
}
@Override
public void insertUpdate(DocumentEvent e) {
toUpperCase(myArea, myDocument);
}
private void toUpperCase(final JTextArea myArea,
final PlainDocument myDocument) {
if (changing) {
return;
}
try {
changing = true;
final String text = myDocument
.getText(0, myDocument.getLength()).toUpperCase();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myArea.setText(text);
changing = false;
}
});
} catch (BadLocationException e1) {
e1.printStackTrace();
}
}
};
myDocument.addDocumentListener(myDocumentListener);
JOptionPane.showMessageDialog(null, new JScrollPane(myArea),
"With DocumentListener", JOptionPane.INFORMATION_MESSAGE);
myDocument.removeDocumentListener(myDocumentListener);
myArea.setText("");
myDocument.setDocumentFilter(new DocumentFilter() {
@Override
public void insertString(FilterBypass fb, int offset, String text,
AttributeSet attr) throws BadLocationException {
text = text.toUpperCase();
super.insertString(fb, offset, text, attr);
}
@Override
public void replace(FilterBypass fb, int offset, int length,
String text, AttributeSet attrs) throws BadLocationException {
text = text.toUpperCase();
super.replace(fb, offset, length, text, attrs);
}
});
JOptionPane.showMessageDialog(null, new JScrollPane(myArea),
"With DocumentFilter", JOptionPane.INFORMATION_MESSAGE);
}
}
A key difference between DocumentListeners and DocumentFilters (and someone correct me if I'm wrong!) is that DocumentListeners fire after the document has been updated while DocumentFilters fire before they have been updated.
精彩评论