Swing - Thread.sleep() останавливает работу JTextField.setText()

Возможный дубликат:
использование sleep() для одного тема

У меня проблемы с JTextField.setText() при использовании Thread.sleep(). Это для простого калькулятора, который я делаю. Когда ввод в поле ввода имеет неправильный формат, я хочу, чтобы «ОШИБКА ВВОДА» отображалась в поле вывода в течение 5 секунд, а затем очищалась. Метод setText() сработал, когда я только что один раз установил для текста значение «ОШИБКА ВВОДА», и, распечатав промежуточный текст, я обнаружил, что он работает как с этим, так и с setText(""). после другого. Проблема возникает, когда я помещаю между ними Thread.sleep(). Вот версия кода SSCCE:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.regex.Pattern;
import javax.swing.*;

public class Calc {
    static Calc calc = new Calc();

    public static void main(String args[]) {
        GUI gui = calc.new GUI();
    }

    public class GUI implements ActionListener {

        private JButton equals;

        private JTextField inputField, outputField;

        public GUI() {
            createFrame();
        }

        public void createFrame() {
            JFrame baseFrame = new JFrame("Calculator");
            baseFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JPanel contentPane = new JPanel();
            BoxLayout layout = new BoxLayout(contentPane, BoxLayout.Y_AXIS);
            contentPane.setLayout(layout);
            baseFrame.setContentPane(contentPane);
            baseFrame.setSize(320, 100);

            equals = new JButton("=");
            equals.addActionListener(this);

            inputField = new JTextField(16);
            inputField.setHorizontalAlignment(JTextField.TRAILING);
            outputField = new JTextField(16);
            outputField.setHorizontalAlignment(JTextField.TRAILING);
            outputField.setEditable(false);

            contentPane.add(inputField);
            contentPane.add(outputField);
            contentPane.add(equals);

            contentPane.getRootPane().setDefaultButton(equals);
            baseFrame.setResizable(false);
            baseFrame.setLocation(100, 100);

            baseFrame.setVisible(true);
        }

        /**
         * When an action event takes place, the source is identified and the
         * appropriate action is taken.
         */

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == equals) {
                inputField.setText(inputField.getText().replaceAll("\\s", ""));
                String text = inputField.getText();
                System.out.println(text);
                Pattern equationPattern = Pattern.compile("[\\d(][\\d-+*/()]+[)\\d]");
                boolean match = equationPattern.matcher(text).matches();
                System.out.println(match);
                if (match) {
                    // Another class calculates
                } else {
                    try {
                        outputField.setText("INPUT ERROR"); // This doesn't appear
                        Thread.sleep(5000);
                        outputField.setText("");
                    } catch (InterruptedException e1) {
                    }
                }
            }
        }
    }
}

На самом деле я не использую вложенный класс, но я хотел, чтобы он мог содержаться в одном классе для вас. Извините за то, как выглядит графический интерфейс, но опять же, это было сделано для сокращения кода. Важный раздел (if (e.getSource() == equals)) остается неизменным по сравнению с моим кодом. Самый простой способ дать неправильный ввод — использовать буквы.


person PElshen    schedule 01.01.2013    source источник
comment
попробуйте поставить System.out.println() там, чтобы увидеть, дойдет ли он до этой точки   -  person tckmn    schedule 01.01.2013
comment
@Doorknob Я сделал это в своем собственном коде, и он достигал этой точки.   -  person PElshen    schedule 01.01.2013


Ответы (3)


Когда вы используете Thread.sleep(), вы делаете это в основном потоке. Это замораживает графический интерфейс на пять секунд, а затем обновляет файл outputField. Когда это происходит, он использует последний заданный текст, который пуст.

Гораздо лучше использовать Swing Timers, и вот пример, который делает то, что вы пытаетесь выполнить:

if (match) {
    // Another class calculates
} else {
    outputField.setText("INPUT ERROR");
    ActionListener listener = new ActionListener(){
        public void actionPerformed(ActionEvent event){
            outputField.setText("");
        }
    };
    Timer timer = new Timer(5000, listener);
    timer.setRepeats(false);
    timer.start();
}
person aly    schedule 01.01.2013
comment
Используйте invokeLater() в TimerTask или, лучше, javax.swing.Timer. - person trashgod; 01.01.2013
comment
@trashgod Хорошая идея, я соответственно изменил код - person aly; 01.01.2013
comment
Отлично работает с моим полным кодом, спасибо @aly - person PElshen; 01.01.2013
comment
Я думаю, что это добавит новый повторяющийся таймер, который будет работать вечно с циклом 5000 мс для каждой ошибки ввода. - person clstrfsck; 01.01.2013
comment
@msandiford Ты прав! Исправлено снова - person aly; 01.01.2013

Как утверждает Филип Уайтхаус в своем ответе, вы блокируете поток отправки событий Swing с помощью вызова Thread.sleep(...).

Учитывая, что вы уже потратили время на настройку ActionListener, вероятно, будет проще всего использовать javax.swing.Timer для управления очисткой текста. Для этого вы можете добавить поле в свой класс GUI:

    private Timer clearTimer = new Timer(5000, this);

В конструкторе для GUI отключите функцию повторов, так как вам действительно нужен только один выстрел:

    public GUI() {
        clearTimer.setRepeats(false);
        createFrame();
    }

Затем можно изменить actionPerformed, чтобы использовать его для запуска таймера/очистки поля:

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == equals) {
            inputField.setText(inputField.getText().replaceAll("\\s", ""));
            String text = inputField.getText();
            System.out.println(text);
            Pattern equationPattern = Pattern.compile("[\\d(][\\d-+*/()]+[)\\d]");
            boolean match = equationPattern.matcher(text).matches();
            System.out.println(match);
            if (match) {
                // Another class calculates
            } else {
                clearTimer.restart();
                outputField.setText("INPUT ERROR"); // This doesn't appear
            }
        } else if (e.getSource() == clearTimer) {
            outputField.setText("");
        }
    }
person clstrfsck    schedule 01.01.2013
comment
Вы правы, рекомендуя использовать Swing Timer, но я предпочитаю не просить ActionListener делать слишком много, так как его отладка и обслуживание могут привести к беспорядку. Почему бы вместо этого просто не использовать анонимные внутренние классы для слушателей? 1+ за хороший совет. - person Hovercraft Full Of Eels; 01.01.2013
comment
Это решение работало в моем примере, но не в моем исходном коде (оно не исчезло), возможно, точка зрения @HovercraftFullOfEels верна в том, что в более сложном коде возникают проблемы. решение Али работает. - person PElshen; 01.01.2013
comment
@PaddyProgrammer: нет, это будет работать в вашем исходном коде, если оно будет реализовано правильно. Ваша проблема, несомненно, заключалась в том, что вы выполнили его рекомендацию. Обратите внимание, что всякий раз, когда вы получаете код в ответах здесь, этот код никогда не подразумевается как решение ваших проблем, а скорее пример кода, идеи которого вы можете использовать для кодового решения, которое вы в конечном итоге создаете сами с этими идеями. - person Hovercraft Full Of Eels; 01.01.2013
comment
@HovercraftFullOfEels Я понимаю, и хотя я не просто добавил решение, я, вероятно, не реализовал его правильно, но, судя по тому, что вы сказали, возможно, это было не лучшее решение. - person PElshen; 01.01.2013
comment
@PaddyProgrammer: все полученные вами решения по сути являются одним и тем же решением, поэтому оно так же хорошо, как и любое другое. Я только что немного поспорил с одной из его деталей реализации (и на самом деле с вашей опубликованной реализацией). Как правило, класс GUI не должен реализовывать интерфейс прослушивателя. Вы никогда не должны использовать this в качестве ActionListener или MouseListener или любого другого такого слушателя. Вам гораздо лучше использовать анонимные внутренние классы для ваших слушателей. - person Hovercraft Full Of Eels; 01.01.2013
comment
FWIW, я согласен с тем, что говорит г-н Ховеркрафт, в том, что код может использовать некоторую реструктуризацию/применение лучших практик. Мое намерение состояло в том, чтобы предоставить правильное решение, требующее минимальных изменений кода. - person clstrfsck; 01.01.2013

Вы делаете Thread.sleep() в основном потоке Swing. Это НЕ хорошая практика. В лучшем случае вам нужно использовать поток SwingWorker.

Происходит то, что он запускает первую строку, нажимая Thread.sleep().

Это не позволяет (основному) потоку EDT выполнять какие-либо перерисовки (а также предотвращает выполнение следующей строки).

Для настроить отложенную реакцию и не помещать sleep() вызовов в основной поток.

person Philip Whitehouse    schedule 01.01.2013
comment
Вы правы, что здесь не следует использовать сон потока, но я не думаю, что сон прерывается. 1+ - person Hovercraft Full Of Eels; 01.01.2013
comment
Это единственная причина, по которой он не будет выполнять второй заданный текст. Неясно, каков был фактический неудачный результат. В любом случае вполне вероятно, что он может быть прерван, что приведет к ошибке. - person Philip Whitehouse; 01.01.2013
comment
Если вы измените его код и распечатаете трассировку стека для InterruptedException, вы увидите, что это никогда не происходит. Вместо этого outputField.setText("INPUT ERROR"); действительно вызывается, Thread.sleep(5000); вызывается, а затем outputField.setText(""); после 5-секундной задержки, но вы никогда не видите INPUT ERROR в JTextField, потому что Thread.sleep(...) переводит поток диспетчеризации событий Swing или EDT в спящий режим. , не позволяя ему выполнять какие-либо действия, включая рисование графического интерфейса, включая содержимое текстового поля. - person Hovercraft Full Of Eels; 01.01.2013
comment
Имеет смысл. Обновленный ответ. - person Philip Whitehouse; 01.01.2013
comment
Спасибо вам обоим за объяснение, теперь я понимаю, почему это происходит - мое понимание потоков ограничено, но это помогло! - person PElshen; 01.01.2013