I am attempting to write a program that will display two Jtextareas, both of which are editible. The goal is to when you edit textAreaRom (entering a roman numeral) the second area (textAreaArab) will display the Arabic Number equivalent. But the thing is I can not get this to happen in real-time. I have read about using DocumentListeners but, this is one of the first times I have ever done GUI programming and I am not to sure on how I would implement it. Anything helps. I am new to GUI stuff as well as StackOverflow so be nice!
Note: My conversion methods all work perfectly.
public class ArabicToRomanGUI_Hard extends JFrame
{
private static final long serialVersionUID = 1L;
private static String input = "";
private static String output = "";
//constructor to add text fields to frame
public ArabicToRomanGUI_Hard()
{
//JFrame frame = new JFrame("Convert Back And Forth");
final JTextField enterRomNumber = new JTextField(20);
final JTextArea textAreaRom = new JTextArea(20,20);
final JTextField enterArabNumber = new JTextField(20);
final JTextArea textAreaArab = new JTextArea(20,20);
setLayout(new FlowLayout());
enterRomNumber.setText("Please enter a Roman numeral");
enterArabNumber.setText("Please enter a Arabic Number");
textAreaRom.setEditable(true);
textAreaArab.setEditable(true);
//textAreaRom.setText(enterRomNumber.getText());
//textAreaArab.setText(enterArabNumber.getText());
if(textAreaRom.isFocusOwner() == true)
{
textAreaRom.addKeyListener(new KeyAdapter()
{
public void keyReleased(KeyEvent e)
{
{
e.getKeyChar();
input += e.getKeyChar();
ConversionLogic_Hard.ConvertFromRomanToArabic(input); //convert
ConversionLogic_Hard.getCheckFail(); //check if conversion is valid.
output = ConversionLogic_Hard.getConvertedRomanNumeral(); //get the conversion
while(ConversionLogic_Hard.getCheckFail() == true && textAreaArab.isFocusOwner() == false)
{
textAreaArab.setText(output);
}
textAreaArab.setText(input);
}
}
});
}
getContentPane().add(enterRomNumber, BorderLayout.EAST);
getContentPane().add(textAreaRom, BorderLayout.WEST);
}
You actually have three problems...
- Updating a second text component when the first is updated, which is, relatively easy.
- Update the first text component when the second is updated...well, that got REAL complicated REAL fast.
- Filter the text as it's been entered. This is actually relatively easy by Implementing a Document Filter
Now, the real problem is #2, this is because you could end up in a nasty place real fast as the first field tries to update the second field, which triggers and event which causes the second field to update the first field which triggers and event which causes the first field to update the second field ... and you start to get the idea.
Fortunately, Swing actually won't let it get that bad, and will throw a IllegalStateException
To over come this, you need to have some kind of way to know when an update to a Document
is been triggered by the slave field or when it's been triggered by something else (like setText
been called, the user typing or pasting text into the field) and ignore some of those events
Now, there's probably a really cool and simply way to do this, but my brain is not in "simple" mode today, sorry.
So, I start with a custom Document
, this basically has a flag which allows me to inspect its state
public class MirrorDocument extends PlainDocument {
private boolean ignoreUpdates;
public void setIgnoreUpdates(boolean ignoreUpdates) {
this.ignoreUpdates = ignoreUpdates;
}
public boolean isIgnoreUpdates() {
return ignoreUpdates;
}
}
Now, I define a DocumentListener
to monitor changes to a Document
. This DocumentListener
takes a "slave" Document
which is used by this listener to update
public static class DocumentHandler implements DocumentListener {
private MirrorDocument slaveDocument;
private boolean ignoreUpdates = false;
public DocumentHandler(MirrorDocument slaveDocument) {
this.slaveDocument = slaveDocument;
}
@Override
public void insertUpdate(DocumentEvent e) {
Document doc = e.getDocument();
if (doc instanceof MirrorDocument) {
MirrorDocument md = (MirrorDocument) doc;
if (!md.isIgnoreUpdates()) {
try {
String text = e.getDocument().getText(e.getOffset(), e.getLength());
slaveDocument.setIgnoreUpdates(true);
slaveDocument.insertString(e.getOffset(), text, null);
} catch (BadLocationException ex) {
ex.printStackTrace();
} finally {
slaveDocument.setIgnoreUpdates(false);
}
}
}
}
@Override
public void removeUpdate(DocumentEvent e) {
Document doc = e.getDocument();
if (doc instanceof MirrorDocument) {
MirrorDocument md = (MirrorDocument) doc;
if (!md.isIgnoreUpdates()) {
try {
slaveDocument.setIgnoreUpdates(true);
slaveDocument.remove(e.getOffset(), e.getLength());
} catch (BadLocationException ex) {
ex.printStackTrace();
} finally {
slaveDocument.setIgnoreUpdates(false);
}
}
}
}
@Override
public void changedUpdate(DocumentEvent e) {
}
}
Now, the really tripy part here is around the ignoreUpdates
flag. Basically, what this does is when a event occurs, first we check the ignoreUpdates
flag of the Document
which triggered the event, if it's false
, we proceeded to set the ignoreUpdates
flag of the slaveDocument
to true
, which prevents it's DocumentListener
from processing any new events and then update the slaveDocument
Okay, this might seem a bit weird, and I'm sorry, but trust me, this will make sense...(one day ... and when it does, you can explain it back to me)
So, next, we need to create everything and glue it altogether...
JTextArea left = new JTextArea(10, 20);
JTextArea right = new JTextArea(10, 20);
MirrorDocument leftDoc = new MirrorDocument();
MirrorDocument rightDoc = new MirrorDocument();
left.setDocument(leftDoc);
right.setDocument(rightDoc);
leftDoc.addDocumentListener(new DocumentHandler(rightDoc));
rightDoc.addDocumentListener(new DocumentHandler(leftDoc));
So we create two JTextArea
s, left
and right
. We create two MirrorDocument
s, one for each JTextArea
, we then create two DocumentHandler
s, one for each JTextArea
and provide the opposite Document
as the slave (rightDoc
for left
and leftDoc
for right
), this allows for the event cross over and updating to occur
This allows us to set up something like...
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
public class MirrorTextAreas {
public static void main(String[] args) {
new MirrorTextAreas();
}
public MirrorTextAreas() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
JTextArea left = new JTextArea(10, 20);
JTextArea right = new JTextArea(10, 20);
setLayout(new GridLayout(1, 2));
add(new JScrollPane(left));
add(new JScrollPane(right));
MirrorDocument leftDoc = new MirrorDocument();
MirrorDocument rightDoc = new MirrorDocument();
left.setDocument(leftDoc);
right.setDocument(rightDoc);
leftDoc.addDocumentListener(new DocumentHandler(rightDoc));
rightDoc.addDocumentListener(new DocumentHandler(leftDoc));
}
}
public class MirrorDocument extends PlainDocument {
private boolean ignoreUpdates;
public void setIgnoreUpdates(boolean ignoreUpdates) {
this.ignoreUpdates = ignoreUpdates;
}
public boolean isIgnoreUpdates() {
return ignoreUpdates;
}
}
public static class DocumentHandler implements DocumentListener {
private MirrorDocument slaveDocument;
private boolean ignoreUpdates = false;
public DocumentHandler(MirrorDocument slaveDocument) {
this.slaveDocument = slaveDocument;
}
@Override
public void insertUpdate(DocumentEvent e) {
Document doc = e.getDocument();
if (doc instanceof MirrorDocument) {
MirrorDocument md = (MirrorDocument) doc;
if (!md.isIgnoreUpdates()) {
try {
String text = e.getDocument().getText(e.getOffset(), e.getLength());
slaveDocument.setIgnoreUpdates(true);
slaveDocument.insertString(e.getOffset(), text, null);
} catch (BadLocationException ex) {
ex.printStackTrace();
} finally {
slaveDocument.setIgnoreUpdates(false);
}
}
}
}
@Override
public void removeUpdate(DocumentEvent e) {
Document doc = e.getDocument();
if (doc instanceof MirrorDocument) {
MirrorDocument md = (MirrorDocument) doc;
if (!md.isIgnoreUpdates()) {
try {
slaveDocument.setIgnoreUpdates(true);
slaveDocument.remove(e.getOffset(), e.getLength());
} catch (BadLocationException ex) {
ex.printStackTrace();
} finally {
slaveDocument.setIgnoreUpdates(false);
}
}
}
}
@Override
public void changedUpdate(DocumentEvent e) {
}
}
}
Okay, so that's about half of what we need, next we need to be able to filter the content been entered into one of the fields so we can change, this can be accomplished by Implementing a Document Filter