1

I wanna make a programmable calculator, i got the basic GUI, and now i'm trying to set up the buttons, and the display. My display text will be "0" basically and if the user type in a number, that number should to be displayed. I tried to do it with KeyListener, but if i press a key it will display the key twice. Why?

 textField.addKeyListener(new KeyListener(){
        boolean newNumber = true;

        public void keyTyped(KeyEvent e) {
        }

        public void keyPressed(KeyEvent e) {
            int keyCode = e.getKeyCode();


            if(keyCode == e.VK_BACK_SPACE && textField.getText().length() == 1){
                textField.setText("0");
                newNumber = true;
                }

            if(textField.getText().equals("0") && newNumber){
                textField.setText(KeyEvent.getKeyText(keyCode));
                newNumber = false;
            }
        }

        public void keyReleased(KeyEvent e) {
        }

    });

Before input:

enter image description here

After "1" input:

enter image description here

AME
  • 302
  • 2
  • 17
  • Don't use a `KeyListener` for this purpose, instead, use a `DocumentFilter`, it's what it's designed for – MadProgrammer Sep 01 '18 at 10:36
  • 2
    In order to understand "why" this is happening, you need to understand "how" text editing works. Essentially what is happening, is, the `keyPressed` event is been delivered to your listener, you are setting the text to the value you want. The key event is then delivered to the listener of the `JTextField`, which is updating the underlying `Document` (which already contains `1`) with the information from the key event, hence it appears twice - this is one of the many reason you don't want to use `KeyListener` and any one that suggests a "work around" for you to continue to do is a hack – MadProgrammer Sep 01 '18 at 10:45
  • @MadProgrammer I guess that something like similar, just i dont know how can i fix it. I already used a DocumentFilter in this program, but to be honest, i really dont understand how it is work, so i can not figure it out by my self. I though that i will be much easier, maybe not that nice, but enough for now. – AME Sep 01 '18 at 10:51
  • 1
    See also this [calculator example](http://stackoverflow.com/a/7441804/418556). It uses `ScriptEngine` to evaluate the expression in the text field. – Andrew Thompson Sep 02 '18 at 01:08

2 Answers2

2

Here a simple solution:

If you use keyPressed, you have to do something in keyReleased and this become complicated. keyTyped is a more simple way.

You can use e.consume() to prevent having the double digit inserted.

    textField.addKeyListener(new KeyListener() {

        int codeDelete = KeyEvent.getExtendedKeyCodeForChar(KeyEvent.VK_DELETE);
        int codeBackSpace = KeyEvent.getExtendedKeyCodeForChar(KeyEvent.VK_BACK_SPACE);

        @Override
        public void keyTyped(KeyEvent e) {

            char keyChar = e.getKeyChar();

            if (textField.getText().length() == 0) {
                textField.setText("0");
            }
            else if (textField.getText().equals("0") && keyChar != codeDelete && keyChar != codeBackSpace) {
                textField.setText(String.valueOf(e.getKeyChar()));
                e.consume();
            }
        }

        @Override
        public void keyPressed(KeyEvent e) {
        }

        @Override
        public void keyReleased(KeyEvent e) {
        }

    });
Arthur
  • 77
  • 6
  • 1
    You can use `KeyEvent.getExtendedKeyCodeForChar(KeyEvent.VK_DELETE)` and `KeyEvent.getExtendedKeyCodeForChar(KeyEvent.VK_BACK_SPACE)` to avoid these magic values. – Stéphane Millien Sep 03 '18 at 00:24
  • 1
    Yes ! It is exactly what I am looking for :-) I will edit my post. Thanks. – Arthur Sep 03 '18 at 00:26
1

For doing that, I derive PlainDocument like this:

import java.awt.EventQueue;
import java.util.regex.Pattern;

import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

public class DigitDocument extends PlainDocument {

    private static final long       serialVersionUID    = 1L;
    protected static final Pattern  patternStartZero    = Pattern.compile("^0.+");

    protected final JTextField      textField;

    private final int               limit;
    private final Runnable          runnableFormat;

    public DigitDocument(JTextField textField, int limit) {

        super();

        this.textField = textField;
        this.limit = limit;

        runnableFormat = new Runnable() {

            @Override
            public void run() {

                String text = textField.getText();

                if (text.length() == 0) {
                    textField.setText("0");
                }
                else if (patternStartZero.matcher(text).matches()) {
                    textField.setText(text.replaceAll("^0+", ""));
                }
            }
        };
    }

    @Override
    public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException {

        str = str.replaceAll("[^0-9]", "");

        if (str.length() == 0)
            return;

        else if ((getLength() + str.length()) <= limit)
            super.insertString(offset, str, attr);

        EventQueue.invokeLater(runnableFormat);
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException {
        if (!"0".equals(textField.getText()))
            super.remove(offs, len);

        EventQueue.invokeLater(runnableFormat);
    }
}

The usage is:

textField.setDocument(new DigitDocument(textField, 10));
textField.setText("0");

In DigitDocument,

  • First arg is the JTextField himself.
  • Second arg (10) is the maximum input length, You can enter only digit.
Stéphane Millien
  • 3,238
  • 22
  • 36
Arthur
  • 77
  • 6
  • 1
    Use a DocumentFilter as has already been suggested. The DocumenFilter is designed so you don't need to create custom Documents. Instead you create the filter and add it to the document. There is no need for all the synchronized code. Swing events execute on the Event Dispatch Thread (EDT) already. – camickr Sep 01 '18 at 14:44
  • 1
    Thanks for edit and comment. invokeLater is still necessary, it doesn't work without. – Arthur Sep 01 '18 at 15:04
  • It wont solve my problem. As i mentioned, i already have a document filter, however i really dont understand exactly, how is it work but i guess it does the same, but not solve my problem. – AME Sep 01 '18 at 16:42