javaswingjtextfieldjtextareatextchanged

Text changeD (not changing) in a JTextPane or JTextField


Ok, I had a very specific problem. I had a panel with a JTextField and a JTextPane (actually 2 JTextPane, but I had only one active) and needed an event to update the relative variables in the caller form when the user was done editing the text.

JTextPane is set with a StyledDocument, therefore, I can't afford to update its variable every single time I press a key, therefore using DocumentListener was not viable.

And using FocusListener was not a solution either, because the variable (couple) was selected using a JList component and it changed the reference before FocusLost was changed. In short, other components were interferring with the variable instance.

This is how I solved the problem.

First of all I created an InputVerifier:

InputVerifier myInputVerifier = new InputVerifier() {
    @Override
    public boolean verify(JComponent input) {
        if ((input instanceof JTextField source) && source == tfKeywords) {
            PropertyChangeEvent evt = new PropertyChangeEvent(source, "Keywords", source.getText(), null);
            for (PropertyChangeListener l: pnlEditorFull.this.getPropertyChangeListeners())
                l.propertyChange(evt);
        } else if (input instanceof JTextPane source && source == ActualEditor) {
            PropertyChangeEvent evt = new PropertyChangeEvent(source, "Description", source.getText(), null);
            for (PropertyChangeListener l: pnlEditorFull.this.getPropertyChangeListeners())
                l.propertyChange(evt);
        }
        return true;
    }
};

When the verifier is asked, it fires an event propertyChange as "Keywords" for the JTextField or "Description" for the JTextArea.

On the other side I have a PropertyListener

pnlEditor.addPropertyChangeListener((PropertyChangeEvent evt) - > {
    if ("Keywords".equals(evt.getPropertyName()))
        myKeywords = EditorPane.getKeywords(); // actually a little more complicated
    else if ("Description".equals(evt.getPropertyName()))
        myDescription = EditorPane.getText(); // actually a little more complicated
});

To retrieve the text I use a function on the panel called getText():

public String getText() {
    StringWriter sw = new StringWriter();
    try {
        ActualEditor.write(sw);
    } catch (IOException ex) {
        LOGGER.log(Level.SEVERE, null, ex);
    }
    return sw.toString();
}

Using ActualEditor.write(sw); is needed because i need to retrieve the text and the formatting.

It worked. It fires before FocusLost and before the JList changes its selected item. Of course I could have just used the JList ListSelectionListener to setup the changes when the selection changed (I initially did it), but this would left an uncertainty window in which, if other components interferred, I could very well lose all the changes or change the wrong variables. It needs to be called when the changes are final (not while the changes were occurring), and that's the only solution I could thing of.

Here a simplified example of how it should work (it also includes the JList):

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import javax.swing.DefaultListModel;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JTextArea;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
 *
 * @author luca.scarcia
 */
public class frmMain extends javax.swing.JFrame {
    
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    static final Logger LOGGER  = System.getLogger(frmMain.class.getName());
    
    private JTextArea anEditor;
    private JList<String> aList;
    private DefaultListModel<String> Model;

    public frmMain() {
        
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        getContentPane().setLayout(new java.awt.GridLayout());

        aList = new JList<>();
        this.add(aList);
        Model = new DefaultListModel<>();
        Model.addElement("Element 1");
        Model.addElement("Element 2");
        Model.addElement("Element 3");
        Model.addElement("Element 4");
        
        aList.setModel(Model);
        
        anEditor = new JTextArea();
        this.add(anEditor);

        anEditor.setInputVerifier(new InputVerifier() {
            @Override
            public boolean verify(JComponent input) {
                if(input == anEditor) {
                    PropertyChangeEvent evt = new PropertyChangeEvent(anEditor, "Description", anEditor.getText(), null);
                    for(PropertyChangeListener l:anEditor.getPropertyChangeListeners()) {
                        l.propertyChange(evt);
                    }
                }
                return true;
            }
        });
        
        anEditor.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
            if(aList.getSelectedIndex()>=0)
                if("Description".equals(evt.getPropertyName()) && evt.getSource()==anEditor) {
                    StringWriter sw = new StringWriter();
                    try {
                        anEditor.write(sw);
                    } catch (IOException ex) {
                        LOGGER.log(Level.ERROR, ex);
                    }
                    Model.setElementAt(sw.toString(), aList.getSelectedIndex());
                    aList.validate();
                }
            }
        });
        
        
        aList.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                if(e.getSource() == aList) {
                    StringReader sr = new StringReader((String)aList.getSelectedValue());
                    try {
                        anEditor.read(sr, null);
                    } catch (IOException ex) {
                        LOGGER.log(Level.ERROR, ex);
                    }
                }
            }
        });
        pack();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            LOGGER.log(Level.ERROR, ex);
        }

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(() -> {
            new frmMain().setVisible(true);
        });
    }
}

Question: Does anybody have a better solution?


Solution

  • And using FocusListener was not a solution either, because the variable (couple) was selected using a JList component and it changed the reference before FocusLost was changed

    You could save the selected index when focus is gained on the text area.

    You then also need to make sure you don't reset the text until the selection listener is finished adjusting:

    import java.awt.event.*;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    import java.io.IOException;
    import java.io.StringReader;
    import java.io.StringWriter;
    import java.lang.System.Logger;
    import java.lang.System.Logger.Level;
    import javax.swing.*;
    import javax.swing.event.ListSelectionEvent;
    import javax.swing.event.ListSelectionListener;
    
    /**
     *
     * @author luca.scarcia
     */
    public class frmMain2 extends javax.swing.JFrame {
    
        private JTextArea anEditor;
        private JList<String> aList;
        private DefaultListModel<String> Model;
    
        public frmMain2() {
    
            setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
            getContentPane().setLayout(new java.awt.GridLayout());
    
            aList = new JList<>();
            this.add(aList);
            Model = new DefaultListModel<>();
            Model.addElement("Element 1");
            Model.addElement("Element 2");
            Model.addElement("Element 3");
            Model.addElement("Element 4");
    
            aList.setModel(Model);
    
            anEditor = new JTextArea(5, 30);
            this.add(anEditor);
    
            anEditor.addFocusListener( new FocusAdapter()
            {
                int selected;
    
                @Override
                public void focusGained(FocusEvent e)
                {
                    selected = aList.getSelectedIndex();
                }
    
                @Override
                public void focusLost(FocusEvent e)
                {
                    Model.setElementAt(anEditor.getText(), selected);
                }
            });
    
            aList.addListSelectionListener(new ListSelectionListener() {
                @Override
                public void valueChanged(ListSelectionEvent e) {
                    if(!e.getValueIsAdjusting()) {
                        StringReader sr = new StringReader((String)aList.getSelectedValue());
                        try {
                            anEditor.read(sr, null);
                        } catch (IOException ex) {
                            System.out.println(ex);
                        }
                    }
                }
            });
    
            pack();
        }
    
        /**
         * @param args the command line arguments
         */
        public static void main(String args[]) {
            try {
                for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                    if ("Nimbus".equals(info.getName())) {
                        javax.swing.UIManager.setLookAndFeel(info.getClassName());
                        break;
                    }
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            }
    
            /* Create and display the form */
            java.awt.EventQueue.invokeLater(() -> {
                new frmMain2().setVisible(true);
            });
        }
    }