0

Edit: The solution to question you think is a possible duplicate(java.lang.IllegalStateException while using Document Listener in TextArea, Java) is exactly what I have entered in the code to come to this problem. I used the 3 override methods when writing the custom DocumentListener. However, this did not solve the problem.

For some reason, this worked okay when using actionListener (See here - Update JTextField.addActionListener without pressing "enter"). The issue with actionListener was I had to hit "enter" every time I wanted the label to update. So someone suggested use DocumentListener instead. Having done this. I am getting an error I cannot figure out, as well as really strange behavior.

Since the code is too large to post here, I have zipped up the project (using with Intellij and JRE 1.8) > https://www.dropbox.com/s/pf4hiuk9y0jby7y/FF7LevelUpStatCalculator.zip?dl=0

Isolated the code block for where the issue is happening (for quick review):

private void setHpBaseStatsTextFieldAction(){
    hpBaseStatsTextField.getDocument().addDocumentListener(
            new DocumentListener() {
                public void insertUpdate(DocumentEvent e) {
                    updateStatGridLabels();
                }
                public void removeUpdate(DocumentEvent e) {
                    updateStatGridLabels();
                }
                public void changedUpdate(DocumentEvent e) {
                    //Plain text components do not fire these events
                }

                public void updateStatGridLabels() {
                    String currCharacter = charSelCombo.getSelectedItem().toString();
                    String checkBaseHpInGui = hpBaseStatsTextField.getText();
                    int baseHpInGui = 0;
                    if (isInteger(checkBaseHpInGui)){
                        baseHpInGui = Integer.parseInt(checkBaseHpInGui);
                    }
                    if ((!(currCharacter.equals(guiCharSelDefaultValue[unselectedDefaultElement]))) && (isInteger(checkBaseHpInGui))){
                        characters[getSelectedCharactersIndex()].setBaseHp(baseHpInGui);
                        hpBaseStatsTextField.setText(Integer.toString(characters[getSelectedCharactersIndex()].getBaseHp()));
                        setHpStatGridRowValues();
                    }
                }
            }
    );
}

Reproduce Issue(s):

Step 1: Run StartGui.java enter image description here

Step 2: Select Cloud in the character combobox enter image description here

Step 3: Enter integer digits and recognize issue(s) enter image description here enter image description here

I noticed it's complaining when trying to access an object and execute a setValue() method on line #1338... Anyone have any ideas why this is happening?

Update:

DocumentFilter seems to be doing it correctly, as long as I comment out a line (that is needed when checking the rules of the max value). Here is the code thus far:

private void setHpBaseStatsTextFieldAction(){
    ((AbstractDocument) hpBaseStatsTextField.getDocument()).setDocumentFilter(
            new DocumentFilter() {
                public void replace(DocumentFilter.FilterBypass fb, int offset, int length,
                                    String text, AttributeSet attrs) throws BadLocationException {
                    if (offset >= fb.getDocument().getLength()) {
                        System.out.println("Added: " + text);
                    } else {
                        String old = fb.getDocument().getText(offset, length);
                        System.out.println("Replaced " + old + " with " + text);
                    }
                    super.replace(fb, offset, length, text, attrs);
                    updateStatGridLabels();
                }

                public void insertString(DocumentFilter.FilterBypass fb, int offset,
                                         String text, AttributeSet attr) throws BadLocationException {
                    System.out.println("Added: " + text);
                    super.insertString(fb, offset, text, attr);
                    updateStatGridLabels();
                }

                public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
                        throws BadLocationException {
                    System.out.println("Removed: " + fb.getDocument().getText(offset, length));
                    super.remove(fb, offset, length);
                    updateStatGridLabels();
                }

                public void updateStatGridLabels() {
                    String currCharacter = charSelCombo.getSelectedItem().toString();
                    String checkBaseHpInGui = hpBaseStatsTextField.getText();
                    int baseHpInGui = 0;
                    if (isInteger(checkBaseHpInGui)){
                        baseHpInGui = Integer.parseInt(checkBaseHpInGui);
                    }
                    if ((!(currCharacter.equals(guiCharSelDefaultValue[unselectedDefaultElement]))) && (isInteger(checkBaseHpInGui))){
                        characters[getSelectedCharactersIndex()].setBaseHp(baseHpInGui);
                        //hpBaseStatsTextField.setText(Integer.toString(characters[getSelectedCharactersIndex()].getBaseHp()));
                        setHpStatGridRowValues();
                    }
                }
            }
    );
}

I could still use some help with this.

Update 2: I started using SwingUtilities.invokeLater as camickr suggested. It works with the line I commented out in the above code snippet, however, it sets it off into an infinite loop...

private void setHpBaseStatsTextFieldAction(){
    ((AbstractDocument) hpBaseStatsTextField.getDocument()).setDocumentFilter(
            new DocumentFilter() {
                boolean newTextReplaceSet = false;
                public void replace(DocumentFilter.FilterBypass fb, int offset, int length,
                                    String text, AttributeSet attrs) throws BadLocationException {
                    if (offset >= fb.getDocument().getLength()) {
                        System.out.println("Added: " + text);
                    } else {
                        String old = fb.getDocument().getText(offset, length);
                        System.out.println("Replaced " + old + " with " + text);
                    }
                    super.replace(fb, offset, length, text, attrs);
                    updateStatGridLabels();
                    newTextReplaceSet = true;
                }
                boolean newTextInsertSet = false;
                public void insertString(DocumentFilter.FilterBypass fb, int offset,
                                         String text, AttributeSet attr) throws BadLocationException {
                    System.out.println("Added: " + text);
                    super.insertString(fb, offset, text, attr);
                    updateStatGridLabels();
                    newTextInsertSet = true;
                }
                boolean newTextRemoveSet = false;
                public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
                        throws BadLocationException {
                    System.out.println("Removed: " + fb.getDocument().getText(offset, length));
                    super.remove(fb, offset, length);
                    updateStatGridLabels();
                    newTextRemoveSet = true;
                }

                public void updateStatGridLabels() {
                    String currCharacter = charSelCombo.getSelectedItem().toString();
                    String checkBaseHpInGui = hpBaseStatsTextField.getText();
                    int baseHpInGui = 0;
                    if (isInteger(checkBaseHpInGui)){
                        baseHpInGui = Integer.parseInt(checkBaseHpInGui);
                    }
                    if ((!(currCharacter.equals(guiCharSelDefaultValue[unselectedDefaultElement]))) && (isInteger(checkBaseHpInGui))){
                        characters[getSelectedCharactersIndex()].setBaseHp(baseHpInGui);
                        if (!newTextReplaceSet || !newTextInsertSet || newTextRemoveSet) {
                            SwingUtilities.invokeLater(new Runnable() {
                                public void run() {
                                    System.out.println("run");
                                    hpBaseStatsTextField.setText(Integer.toString(characters[getSelectedCharactersIndex()].getBaseHp()));
                                }
                            });
                        }
                        //hpBaseStatsTextField.setText(Integer.toString(characters[getSelectedCharactersIndex()].getBaseHp()));
                        setHpStatGridRowValues();
                    }
                }
            }
    );
}

I can't seem to get the combination of boolean flags set right to stop it from doing an infinite loop, but still execute the line System.out.println("run"); with every change made in the JTextField. If I change the if statement from if (!newTextReplaceSet || !newTextInsertSet || newTextRemoveSet) to if (!newTextReplaceSet || newTextInsertSet || newTextRemoveSet), it will go from executing System.out.println("run"); with an infinite loop, to only executing it once (and not again when another change is made to the JTextField). Can anyone help me?

Community
  • 1
  • 1
Fiddle Freak
  • 1,923
  • 5
  • 43
  • 83
  • You seem to try and make modification(s) to the document while it is fireing the documentEvent for a ongoing change. In actionPerformed, the document change is over, there is no conflict, then. – Laurent G May 02 '17 at 13:10
  • 1
    Possible duplicate of [java.lang.IllegalStateException while using Document Listener in TextArea, Java](http://stackoverflow.com/questions/2788779/java-lang-illegalstateexception-while-using-document-listener-in-textarea-java) – Arnaud May 02 '17 at 13:11
  • @Laurent G - it would be nice if there was a way to have it update per-digit-entered by the user. Is there any ways to do this with swing? – Fiddle Freak May 02 '17 at 13:19
  • @FiddleFreak cf http://docs.oracle.com/javase/7/docs/api/javax/swing/text/DocumentFilter.html – Laurent G May 02 '17 at 13:27
  • @Berger the solution to question you think is a possible duplicate is exactly what I have entered in the code to come to this problem. I used the 3 override methods when writing the custom DocumentListener. However, this did not solve the problem... – Fiddle Freak May 02 '17 at 13:28
  • `Since the code is too large to post here,` - The code is NOT too large to post here. We never want to see your entire application. We want to see simple demo code that demonstrates the application. So this means all you need is a frame with a text field and the DocumentListener. You should be able to duplicate the problem in about 20-30 lines of code. This simplified code is called a [mcve] and should be posted with every question. Posting an image of your code is a waste of time. – camickr May 02 '17 at 14:57
  • @camickr I tried duplicating the problem here > http://stackoverflow.com/questions/43737085/update-jtextfield-addactionlistener-without-pressing-enter. But the problem was this code worked (because I wasn't calling an object to set it's own value using it's own method, and instead just doing simply string abstraction). The only way I could really think of to re-produce the error, was to post the application (note: this is just something I am using to help me learn swing and will later be freeware and open-source). – Fiddle Freak May 02 '17 at 14:59
  • @LaurentG I read through here - http://www.java2s.com/Tutorials/Java/Swing_How_to/JTextArea/Create_Custom_document_filter_to_handle_user_action.htm ... but the issue I am having is the same when trying to insert the `updateStatGridLabels();` method. – Fiddle Freak May 02 '17 at 15:01
  • You completely miss the point. If you have a problem in your real application then you need to debug the problem and isolate what causes the problem. Once you do this you will be left with an incredibly small piece of code to post. `because I wasn't calling an object to set it's own value using it's own method` - so why is this code now trying to set its own value? Fix this and the problem will go away. – camickr May 02 '17 at 15:02
  • @camickr same response I gave to Valy - Reason I am doing this is because there are rules (e.g.: baseHp cannot iterate above 9999). These rules are left inside the Characters class, and left out of the Gui. Gui says "hey character, you need to set your value to this". Character says, "Since the value is above my max limit, this is what my value will be set to". Gui says "okay". (I remember watching a speech from Robert C. Martin and him saying leave your business rules out of the Gui, because it's just a delivery mechanism). – Fiddle Freak May 02 '17 at 15:07
  • The issue isn't me setting the value, the issue is how DocumentListener/DocumentFilter handles me setting the value for the object. – Fiddle Freak May 02 '17 at 15:08
  • @FiddleFreak, in English. I'm not going to try to read through your code to understand what "editStringRules" does! `baseHp cannot iterate above 9999` - sounds to me like your want to restrict the text field to 4 characters that are all numeric digits? – camickr May 03 '17 at 01:46
  • @camickr okay, I'll break it down into iterative steps... **Step 1:** User inputs digits as numbers. **Step 2:** For every digit added/removed/replaced in the string value, the entire string is sent to an object. **Step 3:** This object takes the string input value given from the JTextField, and sets it as it's own object.localVariable. **Step 4:** The object takes a look at this variable, if it is less than 0, it sets the local variable value to 0, if it's greater than 9999, it sets the local variable value to 9999, otherwise it leaves it set the way it is. See next comment... – Fiddle Freak May 03 '17 at 02:08
  • **Step 5:** After the object is done setting the value with it's own rules, JTextField calls the object local variable value, to setText() as it's own text field (updating any changes that were made). **Conclusion:** The logic here, is the rules for how the value is set is the objects responsibility (not the Gui aka JTextFIeld). JTextField is responsible for taking user input, handing it to the object, and getting the output from the object to display as it's own text(updated). – Fiddle Freak May 03 '17 at 02:08
  • @FiddleFreak, see edit. – camickr May 03 '17 at 14:29
  • saw it, tried it, still not working. Still hacking away. I give up and will put the business rules in the gui. But even this is not behaving how I want > https://stackoverflow.com/questions/43762409/jtextfield-using-documentfilter-auto-set-to-max-value-if-value-found-is-over-max – Fiddle Freak May 03 '17 at 14:40

2 Answers2

3

For some reason, this worked okay when using actionListener

This is because the ActionListener code is invoked AFTER the Document has been updated.

I used the 3 override methods when writing the custom DocumentListener. However, this did not solve the problem

The problem is that you can't change the Document in the DocumentListener because the Document hasn't been updated with the changed text yet.

The real question is why are you attempting to update a text field as you type text into the text field?

If you really need to do this then there a two common solution:

  1. Don't use a DocumentListener. Instead you can use a DocumentFilter which allows you to manipulate the Document as test is being added/removed.

  2. You need to delay updating the Document. The way you do this is to use SwingUtilities.invokeLater(...). This will place the code on the end of the Event Dispatch Thread (EDT).

baseHp cannot iterate above 9999).

Then this is editing based logic, which should be done in a DocumentFilter. Or you can even use a JFormattedTextField or maybe a JSpinner.

Read the Swing Tutorial. There are examples of using a JFormattedTextField and JSpinner. You will also find example of a DocumentFilter in the `Text Component Features' section.

Edit:

however, it sets it off into an infinite loop...

Well, first you need to understand the difference between using a DocumentListener and a DocumentFilter.

  1. The DocumentListener is used for processing in your application after the data has been added/removed from the Document

  2. A DocumentFilter is used to edit the data before the data is added to the Document

However, the looping problem is the same in both cases and the solution is the same.

The problem is that you:

  1. enter text into the text field
  2. a Listener is invoked
  3. you manipulate the text and use setText() to reset the data
  4. the Listener is invoked again.
  5. ... 3 and 4 keep repeating.

So a solution is to remove the Listener before you invoke the setText(...) method and then restore the Listener.

doc.removeDocumentListener( this );
textfield.setText(...);
doc.addDocumentListener( this );

Although I would say it is not normal to actually change the data entered. Normally you validate the data and display a message if there is an error instead of trying to fix the data and reset the text. By not changing the text field you don't have to worry about causing an infinite loop.

That is why you would use a JFormattedTextField or maybe JSpinner as the editing component. You can easily force the data to be number with a maximum number of digits.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Sounds pretty messy, I guess I'll have to look more into how `SwingUtilities.invokeLater(...)` is done. – Fiddle Freak May 02 '17 at 15:10
  • Updated question, having trouble setting boolean flags with `SwingUtilities.invokeLater(...)`. Could still use help. Thanks. – Fiddle Freak May 02 '17 at 22:13
  • @FiddleFreak, you don't use invokeLater() with a DocumentFilter. All you have done is dump some code and said it doesn't work. I have no idea what you are trying to do so I can't suggest a solution. You need to state an actual requirement when you ask a question. Once we know what the actual requirement is when can suggest whether a DocumentListener or DocumentFilter is a better solution. – camickr May 02 '17 at 23:19
  • "I have no idea what you are trying to do so I can't suggest a solution." Everytime the user types in a new digit, removes a digit, or replaces a digit in the `JTextField`, I am trying to `object.setString(editStringWithRules)` followed by `JTextField.setText(getStringFromObject)`. – Fiddle Freak May 02 '17 at 23:59
0

The problem, as Laurent G stated in the comments, is that you try to change the content of hpBaseStatsTextField while it is firing the DocumentListener change event.

hpBaseStatsTextField.setText([...]);

This line is technically inside the public void insertUpdate(DocumentEvent e) method (even if in a called submethod). Because the text of hpBaseStatsTextField is changed, Java would have to recall the insertUpdate method while already being inside that method. So, this is why you get an exception.

The answer Derek gave here pretty much sums up what I wanted to suggest:

If you want to mutate in the listener you can launch a separate thread to do it later with SwingUtilities.invokeLater. Be careful because the modifications from the separate thread will call the listener again, so set a boolean before launching the thread, return immediately from the listener if it is set and reset it after the modifications have been done in the separate thread.

Anyway, why do you want to change the content of the same text field in which the user is currently typing? This seems a little weird. Maybe have that setText happen on another text field to show the result?

Community
  • 1
  • 1
Valy
  • 573
  • 2
  • 12
  • Reason I am doing this is because there are rules (e.g.: baseHp cannot iterate above 9999). These rules are left inside the Characters class, and left out of the Gui. Gui says "hey character, you need to set your value to this". Character says, "Since the value is above my max limit, this is what my value will be set to". Gui says "okay". (I remember watching a speech from Robert C. Martin and him saying leave your business rules out of the Gui, because it's just a delivery mechanism). – Fiddle Freak May 02 '17 at 15:04
  • 1
    Ok, then you should use `SwingUtilities.invokeLater(...)` or a `DocumentFilter`. – Valy May 02 '17 at 15:20