0

I've looked all over and everyone says that when you want to listen to text changes for a JTextField, or other swing text component, use a DocumentListener on the underlying document. This doesn't help me though because I need to know the component that is using the document, and need to act on it. The DocumentEvent knows what document fired it but the event, nor the document, know what the "parent" component is. IIRC, this is because there can be more than one "parent" for a given document. Here's an example of what I am trying to accomplish.

JTextField txtOne = new JTextField();
JTextField txtTwo = new JTextField();
etc...

KeyListener validator = new KeyListener(){
    public void updateComponent(KeyEvent e) {
        //The line below CAN be accomplished with a docuement listener
        //by grabbing the text of the document.
        boolean valid = validationMethod(((JTextField) e.getSource()).getText());
        if (valid) {
            //This CANNOT be accomplished with a document listener because
            //the document doesn't know what component is using it.
            ((JTextField) e.getSource()).setEnabled(true);
        } else {
            ((JTextField) e.getSource()).setEnabled(false);
        }
    }
    public void keyPressed(KeyEvent e) {updateComponent(e);}
    public void keyReleased(KeyEvent e) {updateComponent(e);}
    public void keyTyped(KeyEvent e) {updateComponent(e);}
};

txtOne.addKeyListener(validator);
txtTwo.addKeyListener(validator);
etc...

The above works great for when text change events come from keyboard interaction. But if I were to do txtTwo.setText("asdfasdf"); it won't fire anything to that listener. Using an ActionListener is even worse because it only fires when the enter key is pressed in most cases. Using a DocumentListener will at least capture every text change, but doesn't seem to work either, unless I'm missing something.

JTextField txtOne = new JTextField();
JTextField txtTwo = new JTextField();
etc...

DocumentListener validator = new DocumentListener() {
    public void updateComponent(DocumentEvent e) {
        boolean valid = validationMethod(e.getDocument().getText(0,
            e.getDocument().getLength()));
        if (valid) {
            //The event has no getSource, only getDocument. The document likewise
            //has no idea what the component is that is using this document.
            ((JTextFieldWithLabel) e.getSource()).setEnabled(true); //won't work
        } else {
            //no idea what i could do here....
            ((JTextFieldWithLabel) e.getSource()).setEnabled(false); //won't work
        }
    }
    public void removeUpdate(DocumentEvent e) {updateComponent(e);}
    public void insertUpdate(DocumentEvent e) {updateComponent(e);}
    public void changedUpdate(DocumentEvent e) {updateComponent(e);}
};

txtOne.getDocument().addDocumentListener(validator);
txtTwo.getDocument().addDocumentListener(validator);
etc...

I need to share a listener because in the end there will be hundreds of components that could potentially use this listener and its functions. I'm not going to copypasta it hundreds of times and hard code each to a specific component.

  • 1
    See [How to find source component that generated a DocumentEvent](http://stackoverflow.com/q/5218731/1048330), it demonstrates a trick with document's `putProperty` and `getProperty`. – tenorsax Nov 05 '13 at 18:36
  • @Aqua Both the first answer and the question that was linked in the first comment answer the problem (although I prefer putProperty to extending a class). However, should I be concerned that swing will automatically change the underlying document object when certain properties of the component change? Oracle seems to elude to this in the second to last paragraph: [Java Tutorial](http://docs.oracle.com/javase/tutorial/uiswing/components/generaltext.html#document) – Woodys Pawn Shop Nov 05 '13 at 19:57
  • According to that tutorial it could only change when calling the setPage method which displays text from a URL. In other words perhaps the document might have to be changed to read HTML. See the second paragraph of the first bullet point under _"Editor Panes vs. Text Panes"_: http://docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html#recap – Radiodef Nov 05 '13 at 20:17
  • @Radiodef Perfect. So setPage and any similar method might change the underlying document. As long as I account for that all will work. Thanks! – Woodys Pawn Shop Nov 05 '13 at 20:30
  • No problem. I added a second solution to my answer that could be better in that it doesn't modify the text components. My answers are of course just an addition to the putProperty/getProperty solution. If you want something that is completely internal in that you don't have to do anything outside the event, the answer @Aqua has linked to is the way to go. – Radiodef Nov 05 '13 at 20:36

1 Answers1

1

You are doing wrong thing with DocumentListener. Each TextComponent has their own Document as their data model. You can't add a DocumentListener to a TextComponent but to a Document and the document event source is a Document, not the text component.

((JTextFieldWithLabel) e.getSource()).setEnabled(true); //won't work

yes, because there is no such function available for DocumentEvent. Well you may use jTextFeild.getDocument().putProperty("owner", jTextFeild); to track the document's owner's text field. If you are worrying about Document changes of the JTextFeild then use an implementation PropertyChangeListener for "document" as with JTextComponent.setDocument() function a property change event is always fired, all we need to do is to listen to this event and reattach our DocumentListener and "owner" property to it:

    class MyDocumentListener implements DocumentListener{

       public void updateComponent(DocumentEvent e)
       {
            boolean valid = checkDataValidity(e.getDocument());
            JTextField txtField = (JTextField) e.getDocument().getProperty("owner");

           if(!valid)
             txtField.setEnabled(false);
           else  txtField.setEnabled(true);
       }

      @Override
      public void insertUpdate(DocumentEvent e) {updateComponent(e);}

      @Override
      public void removeUpdate(DocumentEvent e) {updateComponent(e);}

      @Override
      public void changedUpdate(DocumentEvent e) {}
    }

    class MyPropChangeListener implements PropertyChangeListener{

       DocumentListener documentListenr;

       public MyPropChangeListener(DocumentListener documentListener) {
             this.documentListenr = documentListener;
       }

       @Override
       public void propertyChange(PropertyChangeEvent evt) {
           System.out.println("chaning document!!");
           JTextField txtFeild =  (JTextField)evt.getSource();
           txtFeild.getDocument().putProperty("owner", txtFeild);
           txtFeild.getDocument().addDocumentListener(documentListenr);
        }
     }
   //..............

   MyPropChangeListener propChangeListener = new MyPropChangeListener(new MyDocumentListener());

    jTextField1.addPropertyChangeListener("document", propChangeListener);
    jTextField1.setDocument(new PlainDocument());

    jTextField2.addPropertyChangeListener("document", propChangeListener);
    jTextField2.setDocument(new PlainDocument());

Though i would suggest to use an InputVerifier and DocumentListenertogether for data validation upon text data changes. InputVerifier has boolean verify() for data validation check. It has another function shouldYieldFocus: which is called when the component it is registered going to lose Focus and Focus is transferred only if that method returns true. Actually shouldYieldFocus uses the returned boolean of verify() function to take decision about transferring focus.

Sage
  • 15,290
  • 3
  • 33
  • 38